@ethlete/query 6.0.0-next.2 → 6.0.0-next.20
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 +210 -0
- package/fesm2022/ethlete-query-testing.mjs.map +1 -1
- package/fesm2022/ethlete-query.mjs +1867 -299
- package/fesm2022/ethlete-query.mjs.map +1 -1
- package/generators/migrate-to-query-v3/auth-provider-scaffold.js +131 -0
- package/generators/migrate-to-query-v3/cleanup-migration.js +293 -0
- package/generators/migrate-to-query-v3/http-client-check.js +93 -0
- package/generators/migrate-to-query-v3/legacy-prepare-migration.js +772 -0
- package/generators/migrate-to-query-v3/legacy-query-creator-migration.js +607 -0
- package/generators/migrate-to-query-v3/migration-scope.js +41 -0
- package/generators/migrate-to-query-v3/migration.js +28 -2932
- package/generators/migrate-to-query-v3/module-graph.js +153 -0
- package/generators/migrate-to-query-v3/query-client-migration.js +525 -0
- package/generators/migrate-to-query-v3/rename-symbols.js +193 -0
- package/generators/migrate-to-query-v3/report.js +136 -0
- package/generators/migrate-to-query-v3/schema.json +12 -0
- package/generators/migrate-to-query-v3/shared.js +97 -0
- package/generators/prep-for-query-v3/migration.js +3 -3
- package/package.json +12 -12
- package/types/ethlete-query.d.ts +1057 -36
|
@@ -1,16 +1,137 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { isDevMode, DestroyRef, InjectionToken, inject, Injector, EnvironmentInjector,
|
|
3
|
-
import { createStaticProvider, RuntimeError, clamp, createDestroy, computedTillTruthy, getCookie, setCookie, deleteCookie, createProvider, nextFrame, createRootProvider, isObject, injectRoute, getDomain, injectQueryParamChanges, injectQueryParams, clone, equal, ET_PROPERTY_REMOVED, previousSignalValue } from '@ethlete/core';
|
|
4
|
-
import {
|
|
2
|
+
import { signal, isDevMode, makeEnvironmentProviders, DestroyRef, InjectionToken, inject, Injector, EnvironmentInjector, ElementRef, ErrorHandler, createEnvironmentInjector, effect, untracked, linkedSignal, computed, runInInjectionContext, assertInInjectionContext, ChangeDetectorRef, ViewContainerRef, TemplateRef, Input, Directive, input, Pipe, ViewEncapsulation, Component, resource, PLATFORM_ID, DOCUMENT, NgZone } from '@angular/core';
|
|
3
|
+
import { createStaticProvider, RuntimeError, clamp, createDestroy, computedTillTruthy, getCookie, setCookie, deleteCookie, createProvider, nextFrame, randomId, createRootProvider, isObject, injectRoute, getDomain, injectQueryParamChanges, injectQueryParams, clone, equal, ET_PROPERTY_REMOVED, previousSignalValue } from '@ethlete/core';
|
|
4
|
+
import { filter, Subject, Subscription, Observable, tap, retry, throwError, timer, catchError, takeUntil, startWith, pairwise, switchMap, of, BehaviorSubject, fromEvent, take, ReplaySubject, map, shareReplay, interval, skip, takeWhile, combineLatest, EMPTY, withLatestFrom, debounceTime, firstValueFrom, merge, throttleTime } from 'rxjs';
|
|
5
5
|
import { toObservable, takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
6
|
-
import { HttpClient,
|
|
6
|
+
import { HttpClient, HttpEventType, HttpErrorResponse, HttpHeaders, HttpStatusCode } from '@angular/common/http';
|
|
7
7
|
import { getActiveConsumer, setActiveConsumer } from '@angular/core/primitives/signals';
|
|
8
|
-
import { NgClass, AsyncPipe, JsonPipe } from '@angular/common';
|
|
8
|
+
import { NgClass, AsyncPipe, JsonPipe, isPlatformBrowser } from '@angular/common';
|
|
9
9
|
import * as i1 from '@angular/forms';
|
|
10
10
|
import { FormControl, ReactiveFormsModule, FormGroup } from '@angular/forms';
|
|
11
|
+
import { validateAsync, form } from '@angular/forms/signals';
|
|
11
12
|
import { Router, ActivatedRoute } from '@angular/router';
|
|
12
13
|
import { io } from 'socket.io-client';
|
|
13
14
|
|
|
15
|
+
const noop = () => undefined;
|
|
16
|
+
let devtoolsEnabled = false;
|
|
17
|
+
/**
|
|
18
|
+
* Whether {@link provideQueryDevtools} has been called. When `false`, every `register*` call is a
|
|
19
|
+
* no-op so the devtools instrumentation retains no references and adds no overhead in production.
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
const isQueryDevtoolsEnabled = () => devtoolsEnabled;
|
|
23
|
+
const entries = signal([], /* @ts-ignore */
|
|
24
|
+
...(ngDevMode ? [{ debugName: "entries" }] : /* istanbul ignore next */ []));
|
|
25
|
+
/**
|
|
26
|
+
* The reactive list of every registered devtools entry, consumed by the `<et-query-devtools>` UI.
|
|
27
|
+
*/
|
|
28
|
+
const queryDevtoolsEntries = entries.asReadonly();
|
|
29
|
+
// Per-descriptor counter used to derive stable, reload-deterministic ids. Reset on page load (module
|
|
30
|
+
// re-eval), so the same queries created in the same order get the same ids across reloads — which is
|
|
31
|
+
// what lets the devtools restore the selected query after a reload.
|
|
32
|
+
const idCounters = new Map();
|
|
33
|
+
const descriptorOf = (entry) => {
|
|
34
|
+
const { kind, meta } = entry;
|
|
35
|
+
if (kind === 'query')
|
|
36
|
+
return `query|${meta.clientName ?? ''}|${meta.method ?? ''}|${meta.route ?? ''}`;
|
|
37
|
+
if (kind === 'auth-provider')
|
|
38
|
+
return `auth-provider|${meta.name ?? ''}`;
|
|
39
|
+
if (kind === 'ws-client')
|
|
40
|
+
return `ws-client|${meta.name ?? ''}`;
|
|
41
|
+
return kind;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Registers an entry with the devtools registry. Returns an unregister callback. A no-op (returning
|
|
45
|
+
* a no-op callback) unless {@link provideQueryDevtools} has been called.
|
|
46
|
+
*
|
|
47
|
+
* Ids are derived deterministically from a stable descriptor + a per-descriptor sequence number, so
|
|
48
|
+
* they survive a page reload (letting the UI restore the selected entry) instead of being random.
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
const registerQueryDevtoolsEntry = (entry) => {
|
|
52
|
+
if (!devtoolsEnabled)
|
|
53
|
+
return noop;
|
|
54
|
+
let id = entry.id;
|
|
55
|
+
if (!id) {
|
|
56
|
+
const descriptor = descriptorOf(entry);
|
|
57
|
+
const seq = idCounters.get(descriptor) ?? 0;
|
|
58
|
+
idCounters.set(descriptor, seq + 1);
|
|
59
|
+
id = `${descriptor}#${seq}`;
|
|
60
|
+
}
|
|
61
|
+
const fullEntry = { ...entry, id, createdAt: Date.now() };
|
|
62
|
+
entries.update((list) => [...list, fullEntry]);
|
|
63
|
+
return () => entries.update((list) => list.filter((e) => e.id !== fullEntry.id));
|
|
64
|
+
};
|
|
65
|
+
let suppressStackRegistration = false;
|
|
66
|
+
/**
|
|
67
|
+
* Suppresses devtools registration for the next {@link createQueryStack} call. Used by
|
|
68
|
+
* `createPagedQueryStack` so its backing stack is not registered as a separate `query-stack` entry.
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
const suppressNextQueryStackDevtools = () => {
|
|
72
|
+
suppressStackRegistration = true;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Reads and clears the suppress-next-stack flag. Always call this from `createQueryStack` (even when
|
|
76
|
+
* devtools are disabled) so a suppression request never leaks into a later stack.
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
const consumeSuppressQueryStackDevtools = () => {
|
|
80
|
+
const value = suppressStackRegistration;
|
|
81
|
+
suppressStackRegistration = false;
|
|
82
|
+
return value;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Stringifies a query creator route. Function routes are invoked with an empty args object and any
|
|
86
|
+
* resulting `undefined` path segments are rendered as `:param`, matching the legacy devtools.
|
|
87
|
+
* @internal
|
|
88
|
+
*/
|
|
89
|
+
const stringifyQueryRoute = (route) => {
|
|
90
|
+
if (route === undefined || route === null)
|
|
91
|
+
return '';
|
|
92
|
+
try {
|
|
93
|
+
if (typeof route === 'function') {
|
|
94
|
+
return route({}).replace(/undefined/g, ':param');
|
|
95
|
+
}
|
|
96
|
+
return String(route);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return typeof route === 'function' ? '(dynamic route)' : String(route);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Extracts the human readable client name from a query client tuple. The client's DI token is named
|
|
104
|
+
* `QueryClient_<name>`, so we strip the prefix to recover the original name.
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
const getQueryClientName = (client) => {
|
|
108
|
+
const token = client[2];
|
|
109
|
+
const desc = token?.toString?.() ?? '';
|
|
110
|
+
return desc.replace('InjectionToken ', '').replace('QueryClient_', '') || 'unknown';
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Enables the `@ethlete/query` devtools. Add this to your application providers (e.g. in
|
|
114
|
+
* `bootstrapApplication`) to make query clients, queries, stacks, sequences and auth providers
|
|
115
|
+
* appear in the `<et-query-devtools>` panel.
|
|
116
|
+
*
|
|
117
|
+
* When omitted, all devtools instrumentation is a no-op — no references are retained and there is no
|
|
118
|
+
* runtime overhead — so it is safe to leave out of production builds.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* bootstrapApplication(AppComponent, {
|
|
123
|
+
* providers: [provideQueryDevtools()],
|
|
124
|
+
* });
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
const provideQueryDevtools = () => {
|
|
128
|
+
devtoolsEnabled = true;
|
|
129
|
+
if (!isDevMode()) {
|
|
130
|
+
console.warn('You are using the Query Devtools in production mode. This increases the size of your bundle and should only be used for development purposes.');
|
|
131
|
+
}
|
|
132
|
+
return makeEnvironmentProviders([]);
|
|
133
|
+
};
|
|
134
|
+
|
|
14
135
|
const getOpName = /(query|mutation) ?([\w\d-_]+)? ?\(.*?\)? \{/;
|
|
15
136
|
const transformGql = (str) => {
|
|
16
137
|
const normalizedStr = Array.isArray(str) ? str.join('') : str;
|
|
@@ -46,9 +167,6 @@ const createGqlExecuteFn = (executeOptions) => {
|
|
|
46
167
|
const reset = () => resetExecuteState({ executeState, executeOptions });
|
|
47
168
|
const exec = (executeArgs) => {
|
|
48
169
|
circularChecker.check();
|
|
49
|
-
// Make sure all effects are flushed before reading the args.
|
|
50
|
-
// A withArgs feature effect might still be pending otherwise resulting in the wrong args being read.
|
|
51
|
-
executeOptions.deps.effectScheduler.flush();
|
|
52
170
|
const { args = executeOptions.state.args(), options } = executeArgs ?? {};
|
|
53
171
|
const query = transformGql(executeOptions.creatorInternals.query);
|
|
54
172
|
let gqlParams = query(args?.variables);
|
|
@@ -133,9 +251,9 @@ const setupQueryDependencies = (options) => {
|
|
|
133
251
|
scopeDestroyRef: hostInjector.get(DestroyRef),
|
|
134
252
|
client: hostInjector.get(clientToken),
|
|
135
253
|
injector: undefined, // Will be set after injector creation
|
|
136
|
-
effectScheduler: hostInjector.get(_EffectScheduler),
|
|
137
254
|
ngErrorHandler: hostInjector.get(ErrorHandler),
|
|
138
255
|
httpClient: hostInjector.get(HttpClient),
|
|
256
|
+
hostElement: hostInjector.get(ElementRef, null, { optional: true })?.nativeElement ?? null,
|
|
139
257
|
};
|
|
140
258
|
const queryContext = {
|
|
141
259
|
deps: dependencies,
|
|
@@ -198,6 +316,10 @@ const QueryRuntimeErrorCode = {
|
|
|
198
316
|
INVALID_STATE_INSIDE_SECURE_EXECUTE_FACTORY: 701,
|
|
199
317
|
// Query Execution
|
|
200
318
|
CIRCULAR_QUERY_DEPENDENCY: 800,
|
|
319
|
+
// Query Sequence
|
|
320
|
+
QUERY_SEQUENCE_ALREADY_RUNNING: 900,
|
|
321
|
+
// Legacy interop
|
|
322
|
+
LEGACY_PREPARE_WITHOUT_INJECTION_CONTEXT: 950,
|
|
201
323
|
};
|
|
202
324
|
const queryFeatureUsedMultipleTimes = (type) => {
|
|
203
325
|
return new RuntimeError(QueryRuntimeErrorCode.QUERY_FEATURE_USED_MULTIPLE_TIMES, `The query feature "${type}()" was used multiple times.`);
|
|
@@ -266,6 +388,52 @@ const invalidStateInsideSecureExecuteFactory = () => {
|
|
|
266
388
|
const circularQueryDependency = () => {
|
|
267
389
|
return new RuntimeError(QueryRuntimeErrorCode.CIRCULAR_QUERY_DEPENDENCY, `Query was executed more than 5 times in less than 100ms. This is usually a sign of a circular dependency.`);
|
|
268
390
|
};
|
|
391
|
+
const legacyPrepareWithoutInjectionContext = (creatorName) => {
|
|
392
|
+
const label = creatorName ? `"${creatorName}"` : 'A legacy query creator';
|
|
393
|
+
return new RuntimeError(QueryRuntimeErrorCode.LEGACY_PREPARE_WITHOUT_INJECTION_CONTEXT, `${label}.prepare() 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` +
|
|
394
|
+
`Capture one where a context does exist (field initializer, constructor) and pass it along:\n\n` +
|
|
395
|
+
` private injector = inject(Injector);\n` +
|
|
396
|
+
` search = (term: string) => myQuery.prepare({ queryParams: { term }, injector: this.injector });\n\n` +
|
|
397
|
+
`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.`);
|
|
398
|
+
};
|
|
399
|
+
const querySequenceAlreadyRunning = () => {
|
|
400
|
+
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.`);
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const isClassValidatorError = (error) => {
|
|
404
|
+
return typeof error === 'object' && error !== null && 'statusCode' in error && 'message' in error && 'error' in error;
|
|
405
|
+
};
|
|
406
|
+
const isSymfonyError = (error) => {
|
|
407
|
+
if (!error || typeof error !== 'object') {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
const symfonyError = error;
|
|
411
|
+
return (symfonyError.class !== undefined &&
|
|
412
|
+
symfonyError.detail !== undefined &&
|
|
413
|
+
symfonyError.status !== undefined &&
|
|
414
|
+
symfonyError.title !== undefined &&
|
|
415
|
+
symfonyError.trace !== undefined &&
|
|
416
|
+
symfonyError.type !== undefined);
|
|
417
|
+
};
|
|
418
|
+
const isSymfonyPagerfantaOutOfRangeError = (error) => {
|
|
419
|
+
if (!isSymfonyError(error)) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
return error.class.startsWith('Pagerfanta') && error.class.endsWith('OutOfRangeCurrentPageException');
|
|
423
|
+
};
|
|
424
|
+
const isSymfonyFormViolationListError = (error) => {
|
|
425
|
+
return typeof error === 'object' && error !== null && 'violations' in error;
|
|
426
|
+
};
|
|
427
|
+
const isSymfonyListError = (error) => {
|
|
428
|
+
return (!!error &&
|
|
429
|
+
typeof error === 'object' &&
|
|
430
|
+
Array.isArray(error) &&
|
|
431
|
+
!!error.length &&
|
|
432
|
+
typeof error[0] === 'object' &&
|
|
433
|
+
'message' in error[0] &&
|
|
434
|
+
'propertyPath' in error[0] &&
|
|
435
|
+
'invalidValue' in error[0]);
|
|
436
|
+
};
|
|
269
437
|
|
|
270
438
|
/**
|
|
271
439
|
* Returning this inside a withArgs feature will reset the query args to null.
|
|
@@ -290,6 +458,7 @@ const QueryFeatureType = {
|
|
|
290
458
|
WITH_POLLING: 'WITH_POLLING',
|
|
291
459
|
WITH_AUTO_REFRESH: 'WITH_AUTO_REFRESH',
|
|
292
460
|
WITH_RESPONSE_UPDATE: 'WITH_RESPONSE_UPDATE',
|
|
461
|
+
WITH_PAGE_RESET_ON_ERROR: 'WITH_PAGE_RESET_ON_ERROR',
|
|
293
462
|
};
|
|
294
463
|
const createQueryFeature = (config) => {
|
|
295
464
|
return config;
|
|
@@ -307,17 +476,27 @@ const withArgs = (args) => {
|
|
|
307
476
|
return createQueryFeature({
|
|
308
477
|
type: QueryFeatureType.WITH_ARGS,
|
|
309
478
|
fn: (context) => {
|
|
310
|
-
|
|
479
|
+
// `state.args` is reactively backed by this source, so every reader (exec, polling,
|
|
480
|
+
// auto-refresh) always sees the latest value with no pending-effect window. Sentinels are
|
|
481
|
+
// resolved here: CLEAR -> null, `null` ("don't change") -> keep the previous value.
|
|
482
|
+
let previous = null;
|
|
483
|
+
context.state.subtle.setArgsSource(() => {
|
|
484
|
+
const value = args();
|
|
485
|
+
if (value === CLEAR_QUERY_ARGS) {
|
|
486
|
+
previous = null;
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
if (value === null)
|
|
490
|
+
return previous;
|
|
491
|
+
previous = value;
|
|
492
|
+
return value;
|
|
493
|
+
});
|
|
494
|
+
// Trigger (auto-)execution whenever the resolved args change.
|
|
311
495
|
nestedEffect(() => {
|
|
312
|
-
const currArgsNow =
|
|
496
|
+
const currArgsNow = context.state.args();
|
|
313
497
|
if (currArgsNow === null)
|
|
314
498
|
return;
|
|
315
499
|
untracked(() => {
|
|
316
|
-
if (currArgsNow === CLEAR_QUERY_ARGS) {
|
|
317
|
-
context.state.args.set(null);
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
context.state.args.set(currArgsNow);
|
|
321
500
|
if (context.flags.shouldAutoExecute)
|
|
322
501
|
context.execute({ args: currArgsNow });
|
|
323
502
|
});
|
|
@@ -366,14 +545,9 @@ const withLogging = (options) => {
|
|
|
366
545
|
return createQueryFeature({
|
|
367
546
|
type: QueryFeatureType.WITH_LOGGING,
|
|
368
547
|
fn: (context) => {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
return;
|
|
373
|
-
untracked(() => {
|
|
374
|
-
options.logFn(event);
|
|
375
|
-
});
|
|
376
|
-
}, { injector: context.deps.injector });
|
|
548
|
+
context.state.events$.pipe(takeUntilDestroyed(context.deps.destroyRef)).subscribe((event) => {
|
|
549
|
+
options.logFn(event);
|
|
550
|
+
});
|
|
377
551
|
},
|
|
378
552
|
});
|
|
379
553
|
};
|
|
@@ -382,14 +556,11 @@ const withErrorHandling = (options) => {
|
|
|
382
556
|
return createQueryFeature({
|
|
383
557
|
type: QueryFeatureType.WITH_ERROR_HANDLING,
|
|
384
558
|
fn: (context) => {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
options.handler(error);
|
|
391
|
-
});
|
|
392
|
-
}, { injector: context.deps.injector });
|
|
559
|
+
context.state.events$
|
|
560
|
+
.pipe(filter((event) => event.type === 'error'), takeUntilDestroyed(context.deps.destroyRef))
|
|
561
|
+
.subscribe((event) => {
|
|
562
|
+
options.handler(event.error);
|
|
563
|
+
});
|
|
393
564
|
},
|
|
394
565
|
});
|
|
395
566
|
};
|
|
@@ -398,14 +569,11 @@ const withSuccessHandling = (options) => {
|
|
|
398
569
|
return createQueryFeature({
|
|
399
570
|
type: QueryFeatureType.WITH_SUCCESS_HANDLING,
|
|
400
571
|
fn: (context) => {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
options.handler(response);
|
|
407
|
-
});
|
|
408
|
-
}, { injector: context.deps.injector });
|
|
572
|
+
context.state.events$
|
|
573
|
+
.pipe(filter((event) => event.type === HttpEventType.Response), takeUntilDestroyed(context.deps.destroyRef))
|
|
574
|
+
.subscribe(() => {
|
|
575
|
+
options.handler(context.state.response());
|
|
576
|
+
});
|
|
409
577
|
},
|
|
410
578
|
});
|
|
411
579
|
};
|
|
@@ -456,22 +624,97 @@ const withResponseUpdate = (options) => {
|
|
|
456
624
|
},
|
|
457
625
|
});
|
|
458
626
|
};
|
|
627
|
+
/**
|
|
628
|
+
* Whether an error means the requested page is out of range: HTTP `416`, or a
|
|
629
|
+
* `500` carrying a Symfony/Pagerfanta out-of-range detail (the dev-mode shape).
|
|
630
|
+
* Exported so it can also be used as the predicate of a `withErrorHandling` handler.
|
|
631
|
+
*/
|
|
632
|
+
const isPageOutOfRangeError = (error) => {
|
|
633
|
+
if (error.code === 416)
|
|
634
|
+
return true;
|
|
635
|
+
return error.code === 500 && isSymfonyPagerfantaOutOfRangeError(error.raw.error);
|
|
636
|
+
};
|
|
637
|
+
/**
|
|
638
|
+
* A query feature that resets the page when the current page becomes out of range
|
|
639
|
+
* — e.g. after a filter shrinks the result set below the current page. It reacts
|
|
640
|
+
* to the query's error events and resets the page source; the normal reactive
|
|
641
|
+
* re-execution then runs with the corrected page.
|
|
642
|
+
*
|
|
643
|
+
* Because query args are reactively sourced, this fixes the *source* (a signal or
|
|
644
|
+
* query form field) rather than patching args directly — so the change sticks.
|
|
645
|
+
*
|
|
646
|
+
* @example
|
|
647
|
+
* // signal-driven page
|
|
648
|
+
* const users = getUsers(
|
|
649
|
+
* withArgs(() => ({ queryParams: { page: page() } })),
|
|
650
|
+
* withPageResetOnError({ page }),
|
|
651
|
+
* );
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* // query-form-driven page
|
|
655
|
+
* const users = getUsers(
|
|
656
|
+
* withArgs(() => ({ queryParams: qf.value() })),
|
|
657
|
+
* withPageResetOnError({ reset: () => qf.resetFieldToDefault('page') }),
|
|
658
|
+
* );
|
|
659
|
+
*/
|
|
660
|
+
const withPageResetOnError = (options) => {
|
|
661
|
+
const matches = options.when ?? isPageOutOfRangeError;
|
|
662
|
+
return createQueryFeature({
|
|
663
|
+
type: QueryFeatureType.WITH_PAGE_RESET_ON_ERROR,
|
|
664
|
+
fn: (context) => {
|
|
665
|
+
context.state.events$
|
|
666
|
+
.pipe(filter((event) => event.type === 'error'), takeUntilDestroyed(context.deps.destroyRef))
|
|
667
|
+
.subscribe((event) => {
|
|
668
|
+
if (!matches(event.error))
|
|
669
|
+
return;
|
|
670
|
+
if (options.reset) {
|
|
671
|
+
options.reset();
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
options.page.set(options.resetTo ?? 1);
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
},
|
|
678
|
+
});
|
|
679
|
+
};
|
|
459
680
|
|
|
460
681
|
const setupQueryState = (options) => {
|
|
461
|
-
const request = signal(null,
|
|
462
|
-
|
|
682
|
+
const request = signal(null, /* @ts-ignore */
|
|
683
|
+
...(ngDevMode ? [{ debugName: "request" }] : /* istanbul ignore next */ []));
|
|
684
|
+
const rawResponse = linkedSignal(() => request()?.response() ?? null, /* @ts-ignore */
|
|
685
|
+
...(ngDevMode ? [{ debugName: "rawResponse" }] : /* istanbul ignore next */ []));
|
|
463
686
|
const response = computed(() => {
|
|
464
687
|
const raw = rawResponse();
|
|
465
688
|
if (raw === null)
|
|
466
689
|
return null;
|
|
467
690
|
return options.transformResponse ? options.transformResponse(raw) : raw;
|
|
468
|
-
},
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
const
|
|
691
|
+
}, /* @ts-ignore */
|
|
692
|
+
...(ngDevMode ? [{ debugName: "response" }] : /* istanbul ignore next */ []));
|
|
693
|
+
const loading = linkedSignal(() => request()?.loading() ?? null, /* @ts-ignore */
|
|
694
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
695
|
+
const error = linkedSignal(() => request()?.error() ?? null, /* @ts-ignore */
|
|
696
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
697
|
+
const latestHttpEvent = linkedSignal(() => request()?.currentEvent() ?? null, /* @ts-ignore */
|
|
698
|
+
...(ngDevMode ? [{ debugName: "latestHttpEvent" }] : /* istanbul ignore next */ []));
|
|
699
|
+
// `args` is reactively sourced: reads always reflect the latest value from the installed source
|
|
700
|
+
// (e.g. the `withArgs` feature), so there is no stale window between a source change and an
|
|
701
|
+
// effect writing it. It stays writable (reset / auth-error clear it via `.set(null)`); a manual
|
|
702
|
+
// set is overridden again the next time the source's dependencies change.
|
|
703
|
+
const argsSource = signal(() => null, /* @ts-ignore */
|
|
704
|
+
...(ngDevMode ? [{ debugName: "argsSource" }] : /* istanbul ignore next */ []));
|
|
705
|
+
const args = linkedSignal(() => argsSource()(), /* @ts-ignore */
|
|
706
|
+
...(ngDevMode ? [{ debugName: "args" }] : /* istanbul ignore next */ []));
|
|
707
|
+
const setArgsSource = (source) => argsSource.set(source);
|
|
708
|
+
const lastTimeExecutedAt = signal(null, /* @ts-ignore */
|
|
709
|
+
...(ngDevMode ? [{ debugName: "lastTimeExecutedAt" }] : /* istanbul ignore next */ []));
|
|
710
|
+
const lastTriggeredBy = signal(null, /* @ts-ignore */
|
|
711
|
+
...(ngDevMode ? [{ debugName: "lastTriggeredBy" }] : /* istanbul ignore next */ []));
|
|
712
|
+
const requestEvents$ = new Subject();
|
|
713
|
+
let requestEventsSubscription = Subscription.EMPTY;
|
|
714
|
+
const bindRequestEvents = (request) => {
|
|
715
|
+
requestEventsSubscription.unsubscribe();
|
|
716
|
+
requestEventsSubscription = request.events$.subscribe((event) => requestEvents$.next(event));
|
|
717
|
+
};
|
|
475
718
|
const executionState = computed(() => {
|
|
476
719
|
const currentResponse = response();
|
|
477
720
|
const currentError = error();
|
|
@@ -506,7 +749,8 @@ const setupQueryState = (options) => {
|
|
|
506
749
|
else {
|
|
507
750
|
return null;
|
|
508
751
|
}
|
|
509
|
-
},
|
|
752
|
+
}, /* @ts-ignore */
|
|
753
|
+
...(ngDevMode ? [{ debugName: "executionState" }] : /* istanbul ignore next */ []));
|
|
510
754
|
const state = {
|
|
511
755
|
rawResponse,
|
|
512
756
|
response,
|
|
@@ -517,9 +761,12 @@ const setupQueryState = (options) => {
|
|
|
517
761
|
lastTimeExecutedAt,
|
|
518
762
|
lastTriggeredBy,
|
|
519
763
|
executionState,
|
|
764
|
+
events$: requestEvents$.asObservable(),
|
|
520
765
|
subtle: {
|
|
521
766
|
request,
|
|
522
767
|
rawResponse,
|
|
768
|
+
bindRequestEvents,
|
|
769
|
+
setArgsSource,
|
|
523
770
|
},
|
|
524
771
|
};
|
|
525
772
|
return state;
|
|
@@ -530,7 +777,8 @@ const createQuerySnapshotFn = (options) => {
|
|
|
530
777
|
const context = injectQueryContext();
|
|
531
778
|
const snapshotFn = () => {
|
|
532
779
|
const snapshotState = setupQueryState({});
|
|
533
|
-
const isAlive = signal(true,
|
|
780
|
+
const isAlive = signal(true, /* @ts-ignore */
|
|
781
|
+
...(ngDevMode ? [{ debugName: "isAlive" }] : /* istanbul ignore next */ []));
|
|
534
782
|
const killEffectRef = effect(() => {
|
|
535
783
|
const currentLoading = state.loading();
|
|
536
784
|
const currentError = state.error();
|
|
@@ -549,13 +797,14 @@ const createQuerySnapshotFn = (options) => {
|
|
|
549
797
|
snapshotState.rawResponse.set(currentResponse);
|
|
550
798
|
if (currentLoading)
|
|
551
799
|
return;
|
|
552
|
-
|
|
800
|
+
const hasCompletedResponse = currentLatestHttpEvent?.type === HttpEventType.Response;
|
|
801
|
+
if (!hasCompletedResponse && !currentError)
|
|
553
802
|
return;
|
|
554
803
|
// kill the effect once loading is done and we either have a response or an error
|
|
555
804
|
killEffectRef.destroy();
|
|
556
805
|
isAlive.set(false);
|
|
557
806
|
});
|
|
558
|
-
}, { ...(ngDevMode ? { debugName: "killEffectRef" } : {}), injector: context.deps.injector });
|
|
807
|
+
}, { ...(ngDevMode ? { debugName: "killEffectRef" } : /* istanbul ignore next */ {}), injector: context.deps.injector });
|
|
559
808
|
const snapshot = {
|
|
560
809
|
args: wrapAsObservableSignal(snapshotState.args.asReadonly(), context.deps.injector),
|
|
561
810
|
response: wrapAsObservableSignal(snapshotState.response, context.deps.injector),
|
|
@@ -628,6 +877,8 @@ const createQueryObject = (options) => {
|
|
|
628
877
|
const { state, execute, deps } = options;
|
|
629
878
|
const destroy = () => deps.injector.destroy();
|
|
630
879
|
const setResponse = (response) => state.rawResponse.set(response);
|
|
880
|
+
const setLoading = (loading) => state.loading.set(loading);
|
|
881
|
+
const setError = (error) => state.error.set(error);
|
|
631
882
|
const createSnapshot = createQuerySnapshotFn({ state, execute, deps });
|
|
632
883
|
// Pre-wrap all public signals once so both `query` and `roQuery` share the same instances.
|
|
633
884
|
const wrappedArgs = wrapAsObservableSignal(state.args.asReadonly(), deps.injector);
|
|
@@ -672,6 +923,8 @@ const createQueryObject = (options) => {
|
|
|
672
923
|
subtle: {
|
|
673
924
|
destroy,
|
|
674
925
|
setResponse,
|
|
926
|
+
setLoading,
|
|
927
|
+
setError,
|
|
675
928
|
request: wrappedRequest,
|
|
676
929
|
destroyRef: deps.scopeDestroyRef,
|
|
677
930
|
injector: deps.injector,
|
|
@@ -705,7 +958,28 @@ const createBaseQuery = (options) => {
|
|
|
705
958
|
};
|
|
706
959
|
applyQueryFeatures(options.features, featureFnContext);
|
|
707
960
|
maybeExecute({ execute, flags });
|
|
708
|
-
|
|
961
|
+
const query = createQueryObject({ state, execute, deps });
|
|
962
|
+
if (isQueryDevtoolsEnabled()) {
|
|
963
|
+
const unregister = registerQueryDevtoolsEntry({
|
|
964
|
+
kind: 'query',
|
|
965
|
+
handle: query,
|
|
966
|
+
meta: {
|
|
967
|
+
clientName: getQueryClientName(client),
|
|
968
|
+
clientBaseUrl: deps.client.baseUrl,
|
|
969
|
+
route: stringifyQueryRoute(options.creatorInternals.route ??
|
|
970
|
+
options.creator?.route),
|
|
971
|
+
method: flags.method,
|
|
972
|
+
featureTypes: options.features.map((f) => f.type),
|
|
973
|
+
queryConfig: options.queryConfig,
|
|
974
|
+
creator: options.creator,
|
|
975
|
+
repository: deps.client.repository,
|
|
976
|
+
element: deps.hostElement,
|
|
977
|
+
gqlQuery: options.creatorInternals.query,
|
|
978
|
+
},
|
|
979
|
+
});
|
|
980
|
+
deps.destroyRef.onDestroy(unregister);
|
|
981
|
+
}
|
|
982
|
+
return query;
|
|
709
983
|
});
|
|
710
984
|
};
|
|
711
985
|
|
|
@@ -745,6 +1019,7 @@ const queryExecute = (options) => {
|
|
|
745
1019
|
state.lastTimeExecutedAt.set(Date.now());
|
|
746
1020
|
state.lastTriggeredBy.set(runQueryOptions?.triggeredBy ?? null);
|
|
747
1021
|
state.subtle.request.set(request);
|
|
1022
|
+
state.subtle.bindRequestEvents(request);
|
|
748
1023
|
};
|
|
749
1024
|
const circularQueryDependencyChecker = () => {
|
|
750
1025
|
let lastTriggerTs = 0;
|
|
@@ -778,9 +1053,6 @@ const createExecuteFn = (executeOptions) => {
|
|
|
778
1053
|
const reset = () => resetExecuteState({ executeState, executeOptions });
|
|
779
1054
|
const exec = (executeArgs) => {
|
|
780
1055
|
circularChecker.check();
|
|
781
|
-
// Make sure all effects are flushed before reading the args.
|
|
782
|
-
// A withArgs feature effect might still be pending otherwise resulting in the wrong args being read.
|
|
783
|
-
executeOptions.deps.effectScheduler.flush();
|
|
784
1056
|
const { args = executeOptions.state.args(), options } = executeArgs ?? {};
|
|
785
1057
|
queryExecute({ executeOptions, executeState, args, options });
|
|
786
1058
|
};
|
|
@@ -790,6 +1062,19 @@ const createExecuteFn = (executeOptions) => {
|
|
|
790
1062
|
};
|
|
791
1063
|
|
|
792
1064
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1065
|
+
/**
|
|
1066
|
+
* The empty request-args object, typed to satisfy `RequestArgs<TArgs>` for a query that takes no
|
|
1067
|
+
* arguments — its `QueryArgs` is just `{ response: T }`, so `RequestArgs` resolves to `{}`.
|
|
1068
|
+
*
|
|
1069
|
+
* Only needed in generic code, where `TArgs` is still a type parameter and TS therefore cannot see
|
|
1070
|
+
* that `{}` is the whole of it. Against a concrete query, omit the argument instead: both
|
|
1071
|
+
* `query.execute()` and the auth registry's `execute()` already take none.
|
|
1072
|
+
*
|
|
1073
|
+
* @example
|
|
1074
|
+
* const runAnything = <TArgs extends QueryArgs>(query: Query<TArgs>) =>
|
|
1075
|
+
* query.execute({ args: emptyQueryArgs<TArgs>() });
|
|
1076
|
+
*/
|
|
1077
|
+
const emptyQueryArgs = () => ({});
|
|
793
1078
|
const createQuery = (options) => createBaseQuery({
|
|
794
1079
|
creator: options.creator,
|
|
795
1080
|
creatorInternals: options.creatorInternals,
|
|
@@ -839,41 +1124,6 @@ const createBaseQueryCreator = (config) => {
|
|
|
839
1124
|
return queryCreator;
|
|
840
1125
|
};
|
|
841
1126
|
|
|
842
|
-
const isClassValidatorError = (error) => {
|
|
843
|
-
return typeof error === 'object' && error !== null && 'statusCode' in error && 'message' in error && 'error' in error;
|
|
844
|
-
};
|
|
845
|
-
const isSymfonyError = (error) => {
|
|
846
|
-
if (!error || typeof error !== 'object') {
|
|
847
|
-
return false;
|
|
848
|
-
}
|
|
849
|
-
const symfonyError = error;
|
|
850
|
-
return (symfonyError.class !== undefined &&
|
|
851
|
-
symfonyError.detail !== undefined &&
|
|
852
|
-
symfonyError.status !== undefined &&
|
|
853
|
-
symfonyError.title !== undefined &&
|
|
854
|
-
symfonyError.trace !== undefined &&
|
|
855
|
-
symfonyError.type !== undefined);
|
|
856
|
-
};
|
|
857
|
-
const isSymfonyPagerfantaOutOfRangeError = (error) => {
|
|
858
|
-
if (!isSymfonyError(error)) {
|
|
859
|
-
return false;
|
|
860
|
-
}
|
|
861
|
-
return error.class.startsWith('Pagerfanta') && error.class.endsWith('OutOfRangeCurrentPageException');
|
|
862
|
-
};
|
|
863
|
-
const isSymfonyFormViolationListError = (error) => {
|
|
864
|
-
return typeof error === 'object' && error !== null && 'violations' in error;
|
|
865
|
-
};
|
|
866
|
-
const isSymfonyListError = (error) => {
|
|
867
|
-
return (!!error &&
|
|
868
|
-
typeof error === 'object' &&
|
|
869
|
-
Array.isArray(error) &&
|
|
870
|
-
!!error.length &&
|
|
871
|
-
typeof error[0] === 'object' &&
|
|
872
|
-
'message' in error[0] &&
|
|
873
|
-
'propertyPath' in error[0] &&
|
|
874
|
-
'invalidValue' in error[0]);
|
|
875
|
-
};
|
|
876
|
-
|
|
877
1127
|
const buildErrorMessage = (code, message) => `[@ethlete\\query:${code}] ${message}`;
|
|
878
1128
|
class QueryError extends Error {
|
|
879
1129
|
constructor(code, message, data) {
|
|
@@ -1245,7 +1495,7 @@ const request = (config) => {
|
|
|
1245
1495
|
body = text;
|
|
1246
1496
|
}
|
|
1247
1497
|
}
|
|
1248
|
-
catch
|
|
1498
|
+
catch {
|
|
1249
1499
|
// Ignore the error.
|
|
1250
1500
|
}
|
|
1251
1501
|
}
|
|
@@ -1471,6 +1721,34 @@ const shouldRetryRequest = (options) => {
|
|
|
1471
1721
|
return { retry: false };
|
|
1472
1722
|
};
|
|
1473
1723
|
|
|
1724
|
+
const isQueryErrorResponse = (error) => {
|
|
1725
|
+
return typeof error === 'object' && error !== null && 'raw' in error && 'retryState' in error && 'isList' in error;
|
|
1726
|
+
};
|
|
1727
|
+
/**
|
|
1728
|
+
* Every message carried by an error, flattened into a plain list — one entry for a single error,
|
|
1729
|
+
* one per violation for a list (a form response with several field violations), empty for `null`.
|
|
1730
|
+
*
|
|
1731
|
+
* The single/list split exists because that is how APIs answer; UI almost never wants to branch on
|
|
1732
|
+
* it. Reach for this when rendering, and keep {@link QueryErrorResponse} for anything that needs
|
|
1733
|
+
* the status code or the raw response.
|
|
1734
|
+
*
|
|
1735
|
+
* @example
|
|
1736
|
+
* ```html
|
|
1737
|
+
* @for (message of queryErrorMessages(myQuery.error()); track message) {
|
|
1738
|
+
* <p class="error">{{ message }}</p>
|
|
1739
|
+
* }
|
|
1740
|
+
* ```
|
|
1741
|
+
*/
|
|
1742
|
+
const queryErrorMessages = (error) => {
|
|
1743
|
+
if (!error)
|
|
1744
|
+
return [];
|
|
1745
|
+
return error.isList ? error.errors.map((item) => item.message) : [error.error.message];
|
|
1746
|
+
};
|
|
1747
|
+
/**
|
|
1748
|
+
* The first message of an error, or `null` when there is none — the single-line counterpart to
|
|
1749
|
+
* {@link queryErrorMessages}, for a toast or a form-field hint.
|
|
1750
|
+
*/
|
|
1751
|
+
const queryErrorMessage = (error) => queryErrorMessages(error)[0] ?? null;
|
|
1474
1752
|
const createQueryErrorResponse = (error) => {
|
|
1475
1753
|
let err = error instanceof HttpErrorResponse ? error : null;
|
|
1476
1754
|
if (!err) {
|
|
@@ -1538,20 +1816,46 @@ const SPEED_BUFFER_TIME_IN_MS = 2000;
|
|
|
1538
1816
|
const createHttpRequest = (options) => {
|
|
1539
1817
|
let currentStreamSubscription = Subscription.EMPTY;
|
|
1540
1818
|
const { args, clientOptions, dependencies } = options;
|
|
1541
|
-
const currentEvent = signal(null,
|
|
1542
|
-
|
|
1543
|
-
const
|
|
1544
|
-
const
|
|
1545
|
-
|
|
1546
|
-
const
|
|
1547
|
-
|
|
1548
|
-
const
|
|
1549
|
-
|
|
1819
|
+
const currentEvent = signal(null, /* @ts-ignore */
|
|
1820
|
+
...(ngDevMode ? [{ debugName: "currentEvent" }] : /* istanbul ignore next */ []));
|
|
1821
|
+
const event$ = new Subject();
|
|
1822
|
+
const loading = signal(null, /* @ts-ignore */
|
|
1823
|
+
...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
1824
|
+
const error = signal(null, /* @ts-ignore */
|
|
1825
|
+
...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
1826
|
+
const response = signal(null, /* @ts-ignore */
|
|
1827
|
+
...(ngDevMode ? [{ debugName: "response" }] : /* istanbul ignore next */ []));
|
|
1828
|
+
const lastLoadEventTime = signal(0, /* @ts-ignore */
|
|
1829
|
+
...(ngDevMode ? [{ debugName: "lastLoadEventTime" }] : /* istanbul ignore next */ []));
|
|
1830
|
+
const lastLoadEventAmount = signal(0, /* @ts-ignore */
|
|
1831
|
+
...(ngDevMode ? [{ debugName: "lastLoadEventAmount" }] : /* istanbul ignore next */ []));
|
|
1832
|
+
const lastExecuteTime = signal(0, /* @ts-ignore */
|
|
1833
|
+
...(ngDevMode ? [{ debugName: "lastExecuteTime" }] : /* istanbul ignore next */ []));
|
|
1834
|
+
const expiresIn = signal(null, /* @ts-ignore */
|
|
1835
|
+
...(ngDevMode ? [{ debugName: "expiresIn" }] : /* istanbul ignore next */ []));
|
|
1836
|
+
// NOTE: This must be a plain function, not a `computed`. The freshness check compares against
|
|
1837
|
+
// `Date.now()`, which is not reactive, so a memoized computed would only ever recompute when
|
|
1838
|
+
// `expiresIn` changes — once it evaluated to `false` (fresh) it would stay `false` forever, even
|
|
1839
|
+
// after the window elapsed, turning every `allowCache` execute into a permanent cache hit.
|
|
1840
|
+
const isStale = () => {
|
|
1550
1841
|
const expiresInTs = expiresIn();
|
|
1551
1842
|
return expiresInTs === null || expiresInTs < Date.now();
|
|
1552
|
-
}
|
|
1843
|
+
};
|
|
1844
|
+
// Resolved per execution rather than once at creation, so a client whose headers are a function
|
|
1845
|
+
// reading a signal (a preview token, a tenant id) sees the current value on every re-run.
|
|
1846
|
+
const resolveHeaders = () => {
|
|
1847
|
+
const clientHeaders = typeof options.clientHeaders === 'function' ? options.clientHeaders() : options.clientHeaders;
|
|
1848
|
+
const argHeaders = typeof args?.headers === 'function' ? args.headers() : args?.headers;
|
|
1849
|
+
if (!clientHeaders)
|
|
1850
|
+
return argHeaders;
|
|
1851
|
+
if (!argHeaders)
|
|
1852
|
+
return clientHeaders;
|
|
1853
|
+
// `set` replaces every value of that name, so a per-request header fully overrides the
|
|
1854
|
+
// client-level one instead of being appended next to it.
|
|
1855
|
+
return argHeaders.keys().reduce((merged, key) => merged.set(key, argHeaders.getAll(key) ?? []), clientHeaders);
|
|
1856
|
+
};
|
|
1553
1857
|
const createStream = () => {
|
|
1554
|
-
const headers =
|
|
1858
|
+
const headers = resolveHeaders();
|
|
1555
1859
|
return dependencies.httpClient
|
|
1556
1860
|
.request(options.method, options.fullPath, {
|
|
1557
1861
|
observe: 'events',
|
|
@@ -1577,7 +1881,7 @@ const createHttpRequest = (options) => {
|
|
|
1577
1881
|
}));
|
|
1578
1882
|
};
|
|
1579
1883
|
const execute = (options) => {
|
|
1580
|
-
if (loading() || (!isStale() && options?.allowCache)) {
|
|
1884
|
+
if (!options?.force && (loading() || (!isStale() && options?.allowCache))) {
|
|
1581
1885
|
// Do not execute if there is already a request in progress or caching is allowed
|
|
1582
1886
|
return false;
|
|
1583
1887
|
}
|
|
@@ -1600,14 +1904,12 @@ const createHttpRequest = (options) => {
|
|
|
1600
1904
|
return true;
|
|
1601
1905
|
};
|
|
1602
1906
|
const destroy = () => {
|
|
1603
|
-
|
|
1604
|
-
return false;
|
|
1605
|
-
}
|
|
1907
|
+
const wasActive = !currentStreamSubscription.closed;
|
|
1606
1908
|
currentStreamSubscription.unsubscribe();
|
|
1607
|
-
|
|
1909
|
+
event$.complete();
|
|
1910
|
+
return wasActive;
|
|
1608
1911
|
};
|
|
1609
1912
|
const updateState = (event) => {
|
|
1610
|
-
currentEvent.set(event);
|
|
1611
1913
|
switch (event.type) {
|
|
1612
1914
|
case HttpEventType.Response:
|
|
1613
1915
|
{
|
|
@@ -1634,12 +1936,16 @@ const createHttpRequest = (options) => {
|
|
|
1634
1936
|
}
|
|
1635
1937
|
break;
|
|
1636
1938
|
}
|
|
1939
|
+
currentEvent.set(event);
|
|
1940
|
+
event$.next(event);
|
|
1637
1941
|
};
|
|
1638
1942
|
const updateErrorState = (errorResponse) => {
|
|
1639
1943
|
const errorRes = createQueryErrorResponse(errorResponse);
|
|
1640
1944
|
error.set(errorRes);
|
|
1641
1945
|
loading.set(null);
|
|
1642
|
-
|
|
1946
|
+
const errorEvent = { type: 'error', error: errorRes };
|
|
1947
|
+
currentEvent.set(errorEvent);
|
|
1948
|
+
event$.next(errorEvent);
|
|
1643
1949
|
options.dependencies.ngErrorHandler.handleError(errorRes.raw);
|
|
1644
1950
|
};
|
|
1645
1951
|
const updateLoadingState = (event) => {
|
|
@@ -1673,13 +1979,17 @@ const createHttpRequest = (options) => {
|
|
|
1673
1979
|
loading.set(state);
|
|
1674
1980
|
};
|
|
1675
1981
|
const httpRequest = {
|
|
1982
|
+
method: options.method,
|
|
1983
|
+
url: options.fullPath,
|
|
1676
1984
|
execute,
|
|
1677
1985
|
destroy,
|
|
1678
1986
|
loading: loading.asReadonly(),
|
|
1679
1987
|
error: error.asReadonly(),
|
|
1680
1988
|
response: response.asReadonly(),
|
|
1681
1989
|
currentEvent: currentEvent.asReadonly(),
|
|
1990
|
+
events$: event$.asObservable(),
|
|
1682
1991
|
isStale,
|
|
1992
|
+
expiresAt: expiresIn.asReadonly(),
|
|
1683
1993
|
};
|
|
1684
1994
|
return httpRequest;
|
|
1685
1995
|
};
|
|
@@ -1696,9 +2006,12 @@ const createQueryStack = (options) => {
|
|
|
1696
2006
|
return { queries, lastQuery };
|
|
1697
2007
|
}, transform, argsKeyFn, deduplicateArgs, maxQueries = Infinity, removeStrategy = 'oldest', } = options ?? {};
|
|
1698
2008
|
const injector = inject(Injector);
|
|
1699
|
-
const queries = signal([],
|
|
1700
|
-
|
|
1701
|
-
const
|
|
2009
|
+
const queries = signal([], /* @ts-ignore */
|
|
2010
|
+
...(ngDevMode ? [{ debugName: "queries" }] : /* istanbul ignore next */ []));
|
|
2011
|
+
const lastQuery = signal(null, /* @ts-ignore */
|
|
2012
|
+
...(ngDevMode ? [{ debugName: "lastQuery" }] : /* istanbul ignore next */ []));
|
|
2013
|
+
const firstQuery = computed(() => queries()[0] ?? null, /* @ts-ignore */
|
|
2014
|
+
...(ngDevMode ? [{ debugName: "firstQuery" }] : /* istanbul ignore next */ []));
|
|
1702
2015
|
const hasWithArgsFeature = features.some((f) => f.type == QueryFeatureType.WITH_ARGS);
|
|
1703
2016
|
const hasWithOptimisticUpdateFeature = features.some((f) => f.type == QueryFeatureType.WITH_RESPONSE_UPDATE);
|
|
1704
2017
|
if (hasWithArgsFeature) {
|
|
@@ -1780,23 +2093,29 @@ const createQueryStack = (options) => {
|
|
|
1780
2093
|
untracked(() => clear());
|
|
1781
2094
|
});
|
|
1782
2095
|
effect(() => runWithArgs(args(dependencies?.())));
|
|
1783
|
-
const anyLoading = computed(() => queries().some((q) => q.loading()),
|
|
1784
|
-
|
|
2096
|
+
const anyLoading = computed(() => queries().some((q) => q.loading()), /* @ts-ignore */
|
|
2097
|
+
...(ngDevMode ? [{ debugName: "anyLoading" }] : /* istanbul ignore next */ []));
|
|
2098
|
+
const loadingCount = computed(() => queries().filter((q) => q.loading()).length, /* @ts-ignore */
|
|
2099
|
+
...(ngDevMode ? [{ debugName: "loadingCount" }] : /* istanbul ignore next */ []));
|
|
1785
2100
|
const allLoading = computed(() => {
|
|
1786
2101
|
const qs = queries();
|
|
1787
2102
|
return qs.length > 0 && qs.every((q) => q.loading());
|
|
1788
|
-
},
|
|
2103
|
+
}, /* @ts-ignore */
|
|
2104
|
+
...(ngDevMode ? [{ debugName: "allLoading" }] : /* istanbul ignore next */ []));
|
|
1789
2105
|
const loadingProgress = computed(() => {
|
|
1790
2106
|
const total = queries().length;
|
|
1791
2107
|
const loaded = queries().filter((q) => !q.loading()).length;
|
|
1792
2108
|
return { loaded, total };
|
|
1793
|
-
},
|
|
2109
|
+
}, /* @ts-ignore */
|
|
2110
|
+
...(ngDevMode ? [{ debugName: "loadingProgress" }] : /* istanbul ignore next */ []));
|
|
1794
2111
|
const anyError = computed(() => queries()
|
|
1795
2112
|
.map((q) => q.error())
|
|
1796
|
-
.find((e) => e !== null) ?? null,
|
|
2113
|
+
.find((e) => e !== null) ?? null, /* @ts-ignore */
|
|
2114
|
+
...(ngDevMode ? [{ debugName: "anyError" }] : /* istanbul ignore next */ []));
|
|
1797
2115
|
const errors = computed(() => queries()
|
|
1798
2116
|
.map((q) => q.error())
|
|
1799
|
-
.filter((e) => e !== null),
|
|
2117
|
+
.filter((e) => e !== null), /* @ts-ignore */
|
|
2118
|
+
...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
|
|
1800
2119
|
const response = computed(() => {
|
|
1801
2120
|
const responses = queries().map((q) => q.response());
|
|
1802
2121
|
return transform?.(responses) ?? responses;
|
|
@@ -1831,6 +2150,17 @@ const createQueryStack = (options) => {
|
|
|
1831
2150
|
runWithArgs,
|
|
1832
2151
|
},
|
|
1833
2152
|
};
|
|
2153
|
+
const skipDevtools = consumeSuppressQueryStackDevtools();
|
|
2154
|
+
if (isQueryDevtoolsEnabled() && !skipDevtools) {
|
|
2155
|
+
const unregister = registerQueryDevtoolsEntry({
|
|
2156
|
+
kind: 'query-stack',
|
|
2157
|
+
handle: stack,
|
|
2158
|
+
meta: {
|
|
2159
|
+
featureTypes: features.map((f) => f.type),
|
|
2160
|
+
},
|
|
2161
|
+
});
|
|
2162
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
2163
|
+
}
|
|
1834
2164
|
return stack;
|
|
1835
2165
|
};
|
|
1836
2166
|
|
|
@@ -1891,12 +2221,19 @@ const fakePaginationAdapter = (totalHits = 10) => {
|
|
|
1891
2221
|
};
|
|
1892
2222
|
const createPagedQueryStack = (options) => {
|
|
1893
2223
|
const { responseNormalizer, queryCreator, features } = options;
|
|
1894
|
-
const currentPageArgs = signal(null,
|
|
1895
|
-
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
const
|
|
1899
|
-
|
|
2224
|
+
const currentPageArgs = signal(null, /* @ts-ignore */
|
|
2225
|
+
...(ngDevMode ? [{ debugName: "currentPageArgs" }] : /* istanbul ignore next */ []));
|
|
2226
|
+
const initialPage = signal(options.initialPage ?? 1, /* @ts-ignore */
|
|
2227
|
+
...(ngDevMode ? [{ debugName: "initialPage" }] : /* istanbul ignore next */ []));
|
|
2228
|
+
const loadedMinPage = signal(initialPage(), /* @ts-ignore */
|
|
2229
|
+
...(ngDevMode ? [{ debugName: "loadedMinPage" }] : /* istanbul ignore next */ []));
|
|
2230
|
+
const loadedMaxPage = signal(initialPage(), /* @ts-ignore */
|
|
2231
|
+
...(ngDevMode ? [{ debugName: "loadedMaxPage" }] : /* istanbul ignore next */ []));
|
|
2232
|
+
const resetTrigger = signal(0, /* @ts-ignore */
|
|
2233
|
+
...(ngDevMode ? [{ debugName: "resetTrigger" }] : /* istanbul ignore next */ []));
|
|
2234
|
+
const pageDirection = signal('next', /* @ts-ignore */
|
|
2235
|
+
...(ngDevMode ? [{ debugName: "pageDirection" }] : /* istanbul ignore next */ []));
|
|
2236
|
+
suppressNextQueryStackDevtools();
|
|
1900
2237
|
const stack = createQueryStack({
|
|
1901
2238
|
args: () => null,
|
|
1902
2239
|
queryCreator,
|
|
@@ -1919,7 +2256,7 @@ const createPagedQueryStack = (options) => {
|
|
|
1919
2256
|
transform: transformArrayResponse,
|
|
1920
2257
|
});
|
|
1921
2258
|
effect(() => {
|
|
1922
|
-
|
|
2259
|
+
resetTrigger();
|
|
1923
2260
|
const args = options.args(initialPage(), []);
|
|
1924
2261
|
untracked(() => {
|
|
1925
2262
|
stack.clear();
|
|
@@ -1935,22 +2272,26 @@ const createPagedQueryStack = (options) => {
|
|
|
1935
2272
|
if (!res)
|
|
1936
2273
|
return null;
|
|
1937
2274
|
return responseNormalizer(res, all);
|
|
1938
|
-
},
|
|
2275
|
+
}, /* @ts-ignore */
|
|
2276
|
+
...(ngDevMode ? [{ debugName: "maxPagination" }] : /* istanbul ignore next */ []));
|
|
1939
2277
|
const minPagination = computed(() => {
|
|
1940
2278
|
const res = stack.firstQuery()?.response();
|
|
1941
2279
|
const all = stack.response();
|
|
1942
2280
|
if (!res)
|
|
1943
2281
|
return null;
|
|
1944
2282
|
return responseNormalizer(res, all);
|
|
1945
|
-
},
|
|
2283
|
+
}, /* @ts-ignore */
|
|
2284
|
+
...(ngDevMode ? [{ debugName: "minPagination" }] : /* istanbul ignore next */ []));
|
|
1946
2285
|
const isFirstPageLoaded = computed(() => {
|
|
1947
2286
|
const min = minPagination();
|
|
1948
2287
|
return min ? loadedMinPage() === min.currentPage : false;
|
|
1949
|
-
},
|
|
2288
|
+
}, /* @ts-ignore */
|
|
2289
|
+
...(ngDevMode ? [{ debugName: "isFirstPageLoaded" }] : /* istanbul ignore next */ []));
|
|
1950
2290
|
const isLastPageLoaded = computed(() => {
|
|
1951
2291
|
const max = maxPagination();
|
|
1952
2292
|
return max ? loadedMaxPage() === max.totalPages : false;
|
|
1953
|
-
},
|
|
2293
|
+
}, /* @ts-ignore */
|
|
2294
|
+
...(ngDevMode ? [{ debugName: "isLastPageLoaded" }] : /* istanbul ignore next */ []));
|
|
1954
2295
|
const fetchPreviousPage = () => {
|
|
1955
2296
|
const page = loadedMinPage() - 1;
|
|
1956
2297
|
const allResponses = stack.response();
|
|
@@ -2019,25 +2360,29 @@ const createPagedQueryStack = (options) => {
|
|
|
2019
2360
|
const page = resetOptions?.initialPage ?? initialPage();
|
|
2020
2361
|
initialPage.set(page);
|
|
2021
2362
|
pageDirection.set('next');
|
|
2022
|
-
|
|
2363
|
+
resetTrigger.update((v) => v + 1);
|
|
2023
2364
|
};
|
|
2024
2365
|
const canFetchPreviousPage = computed(() => {
|
|
2025
2366
|
const currentMinPagination = minPagination();
|
|
2026
2367
|
if (!currentMinPagination)
|
|
2027
2368
|
return false;
|
|
2028
2369
|
return loadedMinPage() > 1;
|
|
2029
|
-
},
|
|
2370
|
+
}, /* @ts-ignore */
|
|
2371
|
+
...(ngDevMode ? [{ debugName: "canFetchPreviousPage" }] : /* istanbul ignore next */ []));
|
|
2030
2372
|
const canFetchNextPage = computed(() => {
|
|
2031
2373
|
const currentMaxPagination = maxPagination();
|
|
2032
2374
|
return currentMaxPagination ? loadedMaxPage() < currentMaxPagination.totalPages : false;
|
|
2033
|
-
},
|
|
2375
|
+
}, /* @ts-ignore */
|
|
2376
|
+
...(ngDevMode ? [{ debugName: "canFetchNextPage" }] : /* istanbul ignore next */ []));
|
|
2034
2377
|
const items = computed(() => {
|
|
2035
2378
|
const res = stack.response();
|
|
2036
2379
|
return res.map((r) => responseNormalizer(r, res).items).flat();
|
|
2037
|
-
},
|
|
2380
|
+
}, /* @ts-ignore */
|
|
2381
|
+
...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
2038
2382
|
const isFirstLoad = computed(() => {
|
|
2039
2383
|
return stack.queries().length === 1 && !!stack.lastQuery()?.loading();
|
|
2040
|
-
},
|
|
2384
|
+
}, /* @ts-ignore */
|
|
2385
|
+
...(ngDevMode ? [{ debugName: "isFirstLoad" }] : /* istanbul ignore next */ []));
|
|
2041
2386
|
const execute = (options) => {
|
|
2042
2387
|
const whereFn = options?.where;
|
|
2043
2388
|
if (whereFn) {
|
|
@@ -2096,6 +2441,16 @@ const createPagedQueryStack = (options) => {
|
|
|
2096
2441
|
reset,
|
|
2097
2442
|
execute,
|
|
2098
2443
|
};
|
|
2444
|
+
if (isQueryDevtoolsEnabled()) {
|
|
2445
|
+
const unregister = registerQueryDevtoolsEntry({
|
|
2446
|
+
kind: 'paged-query-stack',
|
|
2447
|
+
handle: pagedQuery,
|
|
2448
|
+
meta: {
|
|
2449
|
+
featureTypes: features?.map((f) => f.type),
|
|
2450
|
+
},
|
|
2451
|
+
});
|
|
2452
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
2453
|
+
}
|
|
2099
2454
|
return pagedQuery;
|
|
2100
2455
|
};
|
|
2101
2456
|
|
|
@@ -2210,7 +2565,8 @@ function effectComputed(computation, injector) {
|
|
|
2210
2565
|
// Ignore errors in the initial computation
|
|
2211
2566
|
// Angular might throw an error if required inputs are read but not available yet
|
|
2212
2567
|
}
|
|
2213
|
-
const lastResult = signal(initialData,
|
|
2568
|
+
const lastResult = signal(initialData, /* @ts-ignore */
|
|
2569
|
+
...(ngDevMode ? [{ debugName: "lastResult" }] : /* istanbul ignore next */ []));
|
|
2214
2570
|
effect(() => {
|
|
2215
2571
|
const data = runInInjectionContext(injector, () => computation());
|
|
2216
2572
|
untracked(() => {
|
|
@@ -2261,7 +2617,8 @@ function queryStateSignal(source, options) {
|
|
|
2261
2617
|
})), {
|
|
2262
2618
|
initialValue: null,
|
|
2263
2619
|
});
|
|
2264
|
-
const rwSignal = linkedSignal(roSignal,
|
|
2620
|
+
const rwSignal = linkedSignal(roSignal, /* @ts-ignore */
|
|
2621
|
+
...(ngDevMode ? [{ debugName: "rwSignal" }] : /* istanbul ignore next */ []));
|
|
2265
2622
|
return rwSignal;
|
|
2266
2623
|
}
|
|
2267
2624
|
function queryStateResponseSignal(source, options) {
|
|
@@ -2269,7 +2626,8 @@ function queryStateResponseSignal(source, options) {
|
|
|
2269
2626
|
const dataSignal = computed(() => {
|
|
2270
2627
|
const state = s();
|
|
2271
2628
|
return isQueryStateSuccess(state) ? state.response : null;
|
|
2272
|
-
},
|
|
2629
|
+
}, /* @ts-ignore */
|
|
2630
|
+
...(ngDevMode ? [{ debugName: "dataSignal" }] : /* istanbul ignore next */ []));
|
|
2273
2631
|
dataSignal['reset'] = () => {
|
|
2274
2632
|
s.set(null);
|
|
2275
2633
|
};
|
|
@@ -2352,7 +2710,8 @@ class V2QueryCreator {
|
|
|
2352
2710
|
};
|
|
2353
2711
|
this.createSignal = (initialValue, config) => {
|
|
2354
2712
|
assertInInjectionContext(this.createSignal);
|
|
2355
|
-
const _signal = signal(initialValue ?? null,
|
|
2713
|
+
const _signal = signal(initialValue ?? null, /* @ts-ignore */
|
|
2714
|
+
...(ngDevMode ? [{ debugName: "_signal" }] : /* istanbul ignore next */ []));
|
|
2356
2715
|
addQueryContainerHandling(toObservable(_signal), () => _signal(), config);
|
|
2357
2716
|
return _signal;
|
|
2358
2717
|
};
|
|
@@ -2622,16 +2981,25 @@ const createSecureExecuteFactory = (options) => {
|
|
|
2622
2981
|
const circularChecker = circularQueryDependencyChecker();
|
|
2623
2982
|
let authQuerySubscription = Subscription.EMPTY;
|
|
2624
2983
|
let tokenRefreshSubscription = Subscription.EMPTY;
|
|
2984
|
+
let tokenWaitSubscription = Subscription.EMPTY;
|
|
2625
2985
|
const reset = () => {
|
|
2626
2986
|
authQuerySubscription.unsubscribe();
|
|
2627
2987
|
authQuerySubscription = Subscription.EMPTY;
|
|
2628
2988
|
tokenRefreshSubscription.unsubscribe();
|
|
2629
2989
|
tokenRefreshSubscription = Subscription.EMPTY;
|
|
2990
|
+
tokenWaitSubscription.unsubscribe();
|
|
2991
|
+
tokenWaitSubscription = Subscription.EMPTY;
|
|
2630
2992
|
resetExecuteState({
|
|
2631
2993
|
executeState,
|
|
2632
2994
|
executeOptions: { deps: options.deps, state: options.state },
|
|
2633
2995
|
});
|
|
2634
2996
|
};
|
|
2997
|
+
// A logout tears down the secure entries in the repository, but the query object holding one keeps
|
|
2998
|
+
// its own `response`/`error` signals — so without this a component still mounted after logout goes
|
|
2999
|
+
// on rendering the previous user's data until something calls `reset()` by hand.
|
|
3000
|
+
options.deps.client.repository.events$
|
|
3001
|
+
.pipe(filter((event) => event.type === 'unbind-all-secure'), takeUntilDestroyed(options.deps.destroyRef))
|
|
3002
|
+
.subscribe(() => reset());
|
|
2635
3003
|
const error = (query) => {
|
|
2636
3004
|
const state = options.state;
|
|
2637
3005
|
state.error.set(query.error());
|
|
@@ -2667,11 +3035,27 @@ const createSecureExecuteFactory = (options) => {
|
|
|
2667
3035
|
},
|
|
2668
3036
|
}, tokens, headers, executeState);
|
|
2669
3037
|
};
|
|
3038
|
+
// The auth query completing and the access token being populated happen on two
|
|
3039
|
+
// different reactive timelines: the token is set by a separate token-extraction
|
|
3040
|
+
// effect (see `setupBearerQueryRegistry`), and for cross-client / secure auth
|
|
3041
|
+
// queries the completion is delivered through several nested `toObservable` hops.
|
|
3042
|
+
// Calling `authAndExec` purely because the auth query is "done" therefore races
|
|
3043
|
+
// the token and can run while it is still null. Gate on the token signal instead:
|
|
3044
|
+
// execute immediately if it is already available, otherwise wait for it.
|
|
3045
|
+
const authAndExecWhenTokenReady = (executeArgs) => {
|
|
3046
|
+
if (options.authProvider.accessToken()) {
|
|
3047
|
+
authAndExec(executeArgs);
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
tokenWaitSubscription.unsubscribe();
|
|
3051
|
+
tokenWaitSubscription = toObservable(options.authProvider.accessToken, {
|
|
3052
|
+
injector: options.deps.injector,
|
|
3053
|
+
})
|
|
3054
|
+
.pipe(filter(Boolean), take(1), tap(() => authAndExec(executeArgs)))
|
|
3055
|
+
.subscribe();
|
|
3056
|
+
};
|
|
2670
3057
|
const exec = (executeArgs) => {
|
|
2671
3058
|
circularChecker.check();
|
|
2672
|
-
// Make sure all effects are flushed before reading the args.
|
|
2673
|
-
// A withArgs feature effect might still be pending otherwise resulting in the wrong args being read.
|
|
2674
|
-
options.deps.effectScheduler.flush();
|
|
2675
3059
|
const execArgsWithDefaults = {
|
|
2676
3060
|
args: executeArgs?.args ?? options.state.args(),
|
|
2677
3061
|
options: executeArgs?.options,
|
|
@@ -2680,6 +3064,8 @@ const createSecureExecuteFactory = (options) => {
|
|
|
2680
3064
|
authQuerySubscription = Subscription.EMPTY;
|
|
2681
3065
|
tokenRefreshSubscription.unsubscribe();
|
|
2682
3066
|
tokenRefreshSubscription = Subscription.EMPTY;
|
|
3067
|
+
tokenWaitSubscription.unsubscribe();
|
|
3068
|
+
tokenWaitSubscription = Subscription.EMPTY;
|
|
2683
3069
|
tokenRefreshSubscription = options.authProvider.afterTokenRefresh$
|
|
2684
3070
|
.pipe(filter(() => {
|
|
2685
3071
|
const currentError = options.state.error();
|
|
@@ -2707,14 +3093,14 @@ const createSecureExecuteFactory = (options) => {
|
|
|
2707
3093
|
error(query);
|
|
2708
3094
|
}
|
|
2709
3095
|
else if (query.response()) {
|
|
2710
|
-
|
|
3096
|
+
authAndExecWhenTokenReady(execArgsWithDefaults);
|
|
2711
3097
|
}
|
|
2712
3098
|
}));
|
|
2713
3099
|
}))
|
|
2714
3100
|
.subscribe();
|
|
2715
3101
|
}
|
|
2716
3102
|
else if (authQuery.response()) {
|
|
2717
|
-
|
|
3103
|
+
authAndExecWhenTokenReady(execArgsWithDefaults);
|
|
2718
3104
|
}
|
|
2719
3105
|
else if (authQuery.error()) {
|
|
2720
3106
|
error(authQuery);
|
|
@@ -2931,10 +3317,18 @@ class LegacyQuery {
|
|
|
2931
3317
|
get _isInMockMode() {
|
|
2932
3318
|
return false;
|
|
2933
3319
|
}
|
|
2934
|
-
constructor(newQuery, _arguments, entity
|
|
3320
|
+
constructor(newQuery, _arguments, entity,
|
|
3321
|
+
/**
|
|
3322
|
+
* Wraps an inert query — see `createInertQuery`. Skips everything that needs a live injector,
|
|
3323
|
+
* so the wrapper is safe to build during teardown.
|
|
3324
|
+
*
|
|
3325
|
+
* @internal
|
|
3326
|
+
*/
|
|
3327
|
+
_isInert = false) {
|
|
2935
3328
|
this.newQuery = newQuery;
|
|
2936
3329
|
this._arguments = _arguments;
|
|
2937
3330
|
this.entity = entity;
|
|
3331
|
+
this._isInert = _isInert;
|
|
2938
3332
|
/**
|
|
2939
3333
|
* @internal
|
|
2940
3334
|
*/
|
|
@@ -2944,22 +3338,11 @@ class LegacyQuery {
|
|
|
2944
3338
|
*/
|
|
2945
3339
|
this._currentPollConfig = null;
|
|
2946
3340
|
/**
|
|
3341
|
+
* `null` for an inert query — there is no live injector to own an effect.
|
|
3342
|
+
*
|
|
2947
3343
|
* @internal
|
|
2948
3344
|
*/
|
|
2949
|
-
this.storeSyncEffect =
|
|
2950
|
-
const res = this.newQuery.response();
|
|
2951
|
-
untracked(() => {
|
|
2952
|
-
if (this.entity?.set) {
|
|
2953
|
-
const id = this.entity.id({ args: this._arguments, response: res });
|
|
2954
|
-
this.entity.set({
|
|
2955
|
-
args: this._arguments,
|
|
2956
|
-
response: res,
|
|
2957
|
-
id,
|
|
2958
|
-
store: this.entity.store,
|
|
2959
|
-
});
|
|
2960
|
-
}
|
|
2961
|
-
});
|
|
2962
|
-
}, ...(ngDevMode ? [{ debugName: "storeSyncEffect" }] : []));
|
|
3345
|
+
this.storeSyncEffect = null;
|
|
2963
3346
|
/**
|
|
2964
3347
|
* @internal
|
|
2965
3348
|
*/
|
|
@@ -2972,15 +3355,50 @@ class LegacyQuery {
|
|
|
2972
3355
|
* @internal
|
|
2973
3356
|
*/
|
|
2974
3357
|
this._isPollingPaused = false;
|
|
3358
|
+
this._isDestroyed = false;
|
|
3359
|
+
if (this._isInert) {
|
|
3360
|
+
this.state$ = of(transformExecStateToQueryState(null));
|
|
3361
|
+
this._isDestroyed = true;
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
2975
3364
|
this.state$ = toObservable(this.newQuery.executionState, { injector: this.newQuery.subtle.injector }).pipe(map((execState) => transformExecStateToQueryState(execState)), switchMap((s) => this._transformState(s)), shareReplay({ bufferSize: 1, refCount: true }));
|
|
2976
|
-
this.
|
|
2977
|
-
|
|
2978
|
-
|
|
3365
|
+
this.storeSyncEffect = effect(() => {
|
|
3366
|
+
const res = this.newQuery.response();
|
|
3367
|
+
untracked(() => {
|
|
3368
|
+
if (this.entity?.set) {
|
|
3369
|
+
const id = this.entity.id({ args: this._arguments, response: res });
|
|
3370
|
+
this.entity.set({
|
|
3371
|
+
args: this._arguments,
|
|
3372
|
+
response: res,
|
|
3373
|
+
id,
|
|
3374
|
+
store: this.entity.store,
|
|
3375
|
+
});
|
|
3376
|
+
}
|
|
3377
|
+
});
|
|
3378
|
+
}, /* @ts-ignore */
|
|
3379
|
+
...(ngDevMode ? [{ debugName: "storeSyncEffect" }] : /* istanbul ignore next */ []));
|
|
3380
|
+
// Tear down only what this wrapper owns. The underlying query is already being destroyed when this fires, so
|
|
3381
|
+
// destroying it again would call `R3Injector.destroy()` on an injector that is mid-teardown, which throws
|
|
3382
|
+
// NG0205 ("Injector has already been destroyed") from inside the view's cleanup phase.
|
|
3383
|
+
this.newQuery.subtle.destroyRef.onDestroy(() => this._teardown());
|
|
3384
|
+
}
|
|
3385
|
+
_teardown() {
|
|
3386
|
+
if (this._isDestroyed) {
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
this._isDestroyed = true;
|
|
2979
3390
|
this._pollingSubscription?.unsubscribe();
|
|
2980
3391
|
this._pollingSubscription = null;
|
|
2981
3392
|
this._dependentsChanged$.complete();
|
|
3393
|
+
this.storeSyncEffect?.destroy();
|
|
3394
|
+
}
|
|
3395
|
+
destroy() {
|
|
3396
|
+
if (this._isDestroyed) {
|
|
3397
|
+
return;
|
|
3398
|
+
}
|
|
3399
|
+
this._teardown();
|
|
3400
|
+
// Re-enters `_teardown` through the query's `destroyRef`, which the `_isDestroyed` guard absorbs.
|
|
2982
3401
|
this.newQuery.subtle.destroy();
|
|
2983
|
-
this.storeSyncEffect.destroy();
|
|
2984
3402
|
}
|
|
2985
3403
|
execute(options = {}) {
|
|
2986
3404
|
untracked(() => this.newQuery.execute({ args: this._arguments, options: { allowCache: options.skipCache !== true } }));
|
|
@@ -2994,7 +3412,8 @@ class LegacyQuery {
|
|
|
2994
3412
|
return this;
|
|
2995
3413
|
}
|
|
2996
3414
|
poll(config) {
|
|
2997
|
-
|
|
3415
|
+
// An inert or already destroyed query has nothing to poll, and the interval would outlive it.
|
|
3416
|
+
if (this._pollingSubscription || this._isDestroyed) {
|
|
2998
3417
|
return this;
|
|
2999
3418
|
}
|
|
3000
3419
|
this._currentPollConfig = config;
|
|
@@ -3079,11 +3498,89 @@ class LegacyQuery {
|
|
|
3079
3498
|
}
|
|
3080
3499
|
}
|
|
3081
3500
|
|
|
3501
|
+
/**
|
|
3502
|
+
* A query that never executes and never leaves its initial state.
|
|
3503
|
+
*
|
|
3504
|
+
* `LegacyQueryCreator.prepare()` hands one back when the injector it was given is already destroyed
|
|
3505
|
+
* — the shape v2 call sites still fire into during teardown (a debounced search resolving after the
|
|
3506
|
+
* user navigated away, an RxJS callback on a stream that has not been unsubscribed yet). v2 had no
|
|
3507
|
+
* injector at all there, so the same code used to be harmless; building a real query would now
|
|
3508
|
+
* throw NG0205 from inside the view's cleanup phase, with no hint about which query caused it.
|
|
3509
|
+
*
|
|
3510
|
+
* Everything reads as "prepared but never executed": `execute()` and friends are no-ops, signals
|
|
3511
|
+
* stay `null`, observables complete immediately. Nothing here touches Angular's DI, which is the
|
|
3512
|
+
* point — there is no live injector left to bind to.
|
|
3513
|
+
*/
|
|
3514
|
+
const createInertQuery = () => {
|
|
3515
|
+
const inertSignal = (value) => Object.assign(signal(value), { asObservable: () => of(value) });
|
|
3516
|
+
const noop = () => {
|
|
3517
|
+
/* the query is inert — there is nothing to execute, reset or destroy */
|
|
3518
|
+
};
|
|
3519
|
+
const base = {
|
|
3520
|
+
args: inertSignal(null),
|
|
3521
|
+
response: inertSignal(null),
|
|
3522
|
+
latestHttpEvent: inertSignal(null),
|
|
3523
|
+
loading: inertSignal(null),
|
|
3524
|
+
error: inertSignal(null),
|
|
3525
|
+
lastTimeExecutedAt: inertSignal(null),
|
|
3526
|
+
triggeredBy: inertSignal(null),
|
|
3527
|
+
id: inertSignal(null),
|
|
3528
|
+
executionState: inertSignal(null),
|
|
3529
|
+
};
|
|
3530
|
+
const createSnapshot = () => ({ ...base, isAlive: inertSignal(false) });
|
|
3531
|
+
// A DestroyRef that is already destroyed: callbacks registered on it are dropped rather than
|
|
3532
|
+
// stored, so nothing keeps this object (or its registrant) alive.
|
|
3533
|
+
const destroyRef = {
|
|
3534
|
+
destroyed: true,
|
|
3535
|
+
onDestroy: () => noop,
|
|
3536
|
+
};
|
|
3537
|
+
return {
|
|
3538
|
+
...base,
|
|
3539
|
+
execute: noop,
|
|
3540
|
+
createSnapshot,
|
|
3541
|
+
reset: noop,
|
|
3542
|
+
asReadonly: () => ({ ...base, createSnapshot }),
|
|
3543
|
+
subtle: {
|
|
3544
|
+
destroy: noop,
|
|
3545
|
+
setResponse: noop,
|
|
3546
|
+
setLoading: noop,
|
|
3547
|
+
setError: noop,
|
|
3548
|
+
request: inertSignal(null),
|
|
3549
|
+
destroyRef,
|
|
3550
|
+
injector: Injector.NULL,
|
|
3551
|
+
},
|
|
3552
|
+
};
|
|
3553
|
+
};
|
|
3554
|
+
|
|
3555
|
+
/**
|
|
3556
|
+
* Whether an injector can still be used. A destroyed `R3Injector` throws on `get()` rather than
|
|
3557
|
+
* answering, so the throw itself is the answer; a node injector hands back a `DestroyRef` that
|
|
3558
|
+
* reports its own state.
|
|
3559
|
+
*/
|
|
3560
|
+
const isInjectorUsable = (injector) => {
|
|
3561
|
+
try {
|
|
3562
|
+
return injector.get(DestroyRef, null, { optional: true })?.destroyed !== true;
|
|
3563
|
+
}
|
|
3564
|
+
catch {
|
|
3565
|
+
return false;
|
|
3566
|
+
}
|
|
3567
|
+
};
|
|
3082
3568
|
class LegacyQueryCreator {
|
|
3083
3569
|
constructor(options) {
|
|
3084
3570
|
this.options = options;
|
|
3085
3571
|
this.prepare = (args) => {
|
|
3086
|
-
|
|
3572
|
+
// v2's `prepare()` needed no injection context, so plenty of call sites are plain callbacks.
|
|
3573
|
+
// Angular's raw NG0203 names neither the query nor the call site, which makes those a bisection
|
|
3574
|
+
// exercise — replace it with an error that does.
|
|
3575
|
+
const injector = args?.injector ??
|
|
3576
|
+
(() => {
|
|
3577
|
+
try {
|
|
3578
|
+
return inject(Injector);
|
|
3579
|
+
}
|
|
3580
|
+
catch {
|
|
3581
|
+
throw legacyPrepareWithoutInjectionContext(this.options.name);
|
|
3582
|
+
}
|
|
3583
|
+
})();
|
|
3087
3584
|
let headers = new HttpHeaders();
|
|
3088
3585
|
if (args?.headers) {
|
|
3089
3586
|
Object.entries(args.headers).forEach(([key, value]) => {
|
|
@@ -3098,6 +3595,17 @@ class LegacyQueryCreator {
|
|
|
3098
3595
|
...(args?.queryParams ? { queryParams: args.queryParams } : {}),
|
|
3099
3596
|
headers,
|
|
3100
3597
|
};
|
|
3598
|
+
// A captured component injector routinely outlives its component here: a debounced search
|
|
3599
|
+
// resolving after navigation, an RxJS callback on a stream torn down a tick later. That is not
|
|
3600
|
+
// a programming error the way it would be in new code — v2 had no injector to destroy — so it
|
|
3601
|
+
// yields a query that does nothing instead of an NG0205 raised from the view's cleanup phase.
|
|
3602
|
+
if (!isInjectorUsable(injector)) {
|
|
3603
|
+
if (isDevMode()) {
|
|
3604
|
+
console.warn(`${this.options.name ? `"${this.options.name}"` : 'A legacy query'}.prepare() was called with a destroyed injector, so an inert query was returned. ` +
|
|
3605
|
+
`This usually means a callback outlived the component whose injector it captured — guard the call site with DestroyRef.onDestroy, or pass an injector that outlives it.`);
|
|
3606
|
+
}
|
|
3607
|
+
return new LegacyQuery(createInertQuery(), queryArgs, this.options.entity, true);
|
|
3608
|
+
}
|
|
3101
3609
|
return runInInjectionContext(injector, () => {
|
|
3102
3610
|
return untracked(() => {
|
|
3103
3611
|
const newQuery = this.options.creator({
|
|
@@ -3112,7 +3620,8 @@ class LegacyQueryCreator {
|
|
|
3112
3620
|
legacyQuery.destroy();
|
|
3113
3621
|
destroyEffect.destroy();
|
|
3114
3622
|
}
|
|
3115
|
-
},
|
|
3623
|
+
}, /* @ts-ignore */
|
|
3624
|
+
...(ngDevMode ? [{ debugName: "destroyEffect" }] : /* istanbul ignore next */ []));
|
|
3116
3625
|
}
|
|
3117
3626
|
return legacyQuery;
|
|
3118
3627
|
});
|
|
@@ -3124,7 +3633,8 @@ class LegacyQueryCreator {
|
|
|
3124
3633
|
return subject;
|
|
3125
3634
|
};
|
|
3126
3635
|
this.createSignal = (initialValue, config) => {
|
|
3127
|
-
const _signal = signal(initialValue ?? null,
|
|
3636
|
+
const _signal = signal(initialValue ?? null, /* @ts-ignore */
|
|
3637
|
+
...(ngDevMode ? [{ debugName: "_signal" }] : /* istanbul ignore next */ []));
|
|
3128
3638
|
addQueryContainerHandling(toObservable(_signal), () => _signal(), config);
|
|
3129
3639
|
return _signal;
|
|
3130
3640
|
};
|
|
@@ -3263,15 +3773,20 @@ const isQueryCollection = (query) => {
|
|
|
3263
3773
|
const createQueryCollection = (
|
|
3264
3774
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3265
3775
|
queryMap) => new BehaviorSubject(null);
|
|
3266
|
-
const createQueryCollectionSubject = (
|
|
3776
|
+
const createQueryCollectionSubject = (
|
|
3777
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3778
|
+
queryMap, config) => {
|
|
3267
3779
|
assertInInjectionContext(createQueryCollectionSubject);
|
|
3268
3780
|
const subject = new BehaviorSubject(null);
|
|
3269
3781
|
addQueryContainerHandling(subject.pipe(map((s) => s?.query ?? null)), () => subject.getValue()?.query ?? null, config);
|
|
3270
3782
|
return subject;
|
|
3271
3783
|
};
|
|
3272
|
-
const createQueryCollectionSignal = (
|
|
3784
|
+
const createQueryCollectionSignal = (
|
|
3785
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
3786
|
+
queryMap, config) => {
|
|
3273
3787
|
assertInInjectionContext(createQueryCollectionSignal);
|
|
3274
|
-
const _signal = signal(null,
|
|
3788
|
+
const _signal = signal(null, /* @ts-ignore */
|
|
3789
|
+
...(ngDevMode ? [{ debugName: "_signal" }] : /* istanbul ignore next */ []));
|
|
3275
3790
|
addQueryContainerHandling(toObservable(_signal).pipe(map((s) => s?.query ?? null)), () => _signal()?.query ?? null, config);
|
|
3276
3791
|
return _signal;
|
|
3277
3792
|
};
|
|
@@ -4005,7 +4520,8 @@ class V2BearerAuthProvider {
|
|
|
4005
4520
|
}
|
|
4006
4521
|
|
|
4007
4522
|
const [provideInfinityQueryResponseDelay, injectInfinityQueryResponseDelay] = createProvider(() => {
|
|
4008
|
-
const enabled = signal(false,
|
|
4523
|
+
const enabled = signal(false, /* @ts-ignore */
|
|
4524
|
+
...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
|
|
4009
4525
|
const enabled$ = toObservable(enabled);
|
|
4010
4526
|
return {
|
|
4011
4527
|
enabled,
|
|
@@ -4214,7 +4730,7 @@ class InfinityQueryDirective {
|
|
|
4214
4730
|
get data$() {
|
|
4215
4731
|
return this._data$.asObservable();
|
|
4216
4732
|
}
|
|
4217
|
-
static ngTemplateContextGuard(
|
|
4733
|
+
static ngTemplateContextGuard(_dir, _ctx) {
|
|
4218
4734
|
return true;
|
|
4219
4735
|
}
|
|
4220
4736
|
constructor() {
|
|
@@ -4316,13 +4832,13 @@ class InfinityQueryDirective {
|
|
|
4316
4832
|
this._viewContext.currentQuery = null;
|
|
4317
4833
|
this._data$.next([]);
|
|
4318
4834
|
}
|
|
4319
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4320
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "
|
|
4835
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: InfinityQueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
4836
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.7", type: InfinityQueryDirective, isStandalone: true, selector: "[etInfinityQuery]", inputs: { infinityQuery: ["etInfinityQuery", "infinityQuery"] }, providers: [
|
|
4321
4837
|
{ provide: INFINITY_QUERY_TOKEN, useExisting: InfinityQueryDirective },
|
|
4322
4838
|
provideInfinityQueryResponseDelay(),
|
|
4323
4839
|
], exportAs: ["etInfinityQuery"], ngImport: i0 }); }
|
|
4324
4840
|
}
|
|
4325
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
4841
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: InfinityQueryDirective, decorators: [{
|
|
4326
4842
|
type: Directive,
|
|
4327
4843
|
args: [{
|
|
4328
4844
|
selector: '[etInfinityQuery]',
|
|
@@ -4344,10 +4860,13 @@ class InfinityQueryTriggerDirective {
|
|
|
4344
4860
|
this._destroy = new Subject();
|
|
4345
4861
|
this._observer = null;
|
|
4346
4862
|
this.click$ = fromEvent(this._elementRef.nativeElement, 'click');
|
|
4347
|
-
this.scrollContainerSelector = input(null,
|
|
4863
|
+
this.scrollContainerSelector = input(null, /* @ts-ignore */
|
|
4864
|
+
...(ngDevMode ? [{ debugName: "scrollContainerSelector" }] : /* istanbul ignore next */ []));
|
|
4348
4865
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4349
|
-
this.infinityQueryOverride = input(null,
|
|
4350
|
-
|
|
4866
|
+
this.infinityQueryOverride = input(null, /* @ts-ignore */
|
|
4867
|
+
...(ngDevMode ? [{ debugName: "infinityQueryOverride" }] : /* istanbul ignore next */ []));
|
|
4868
|
+
this.infinityQuery = computed(() => this.infinityQueryOverride() ?? this._infinityQuery, /* @ts-ignore */
|
|
4869
|
+
...(ngDevMode ? [{ debugName: "infinityQuery" }] : /* istanbul ignore next */ []));
|
|
4351
4870
|
}
|
|
4352
4871
|
ngOnInit() {
|
|
4353
4872
|
const isInteractive = this._elementRef.nativeElement.tagName === 'BUTTON';
|
|
@@ -4379,10 +4898,10 @@ class InfinityQueryTriggerDirective {
|
|
|
4379
4898
|
});
|
|
4380
4899
|
this._observer.observe(this._elementRef.nativeElement);
|
|
4381
4900
|
}
|
|
4382
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4383
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "
|
|
4901
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: InfinityQueryTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
4902
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.7", type: InfinityQueryTriggerDirective, isStandalone: true, selector: "[etInfinityQueryTrigger], et-infinity-query-trigger", inputs: { scrollContainerSelector: { classPropertyName: "scrollContainerSelector", publicName: "scrollContainerSelector", isSignal: true, isRequired: false, transformFunction: null }, infinityQueryOverride: { classPropertyName: "infinityQueryOverride", publicName: "infinityQueryOverride", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
4384
4903
|
}
|
|
4385
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
4904
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: InfinityQueryTriggerDirective, decorators: [{
|
|
4386
4905
|
type: Directive,
|
|
4387
4906
|
args: [{
|
|
4388
4907
|
selector: '[etInfinityQueryTrigger], et-infinity-query-trigger',
|
|
@@ -4390,7 +4909,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImpor
|
|
|
4390
4909
|
}], propDecorators: { scrollContainerSelector: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollContainerSelector", required: false }] }], infinityQueryOverride: [{ type: i0.Input, args: [{ isSignal: true, alias: "infinityQueryOverride", required: false }] }] } });
|
|
4391
4910
|
|
|
4392
4911
|
class QueryDirective {
|
|
4393
|
-
static ngTemplateContextGuard(
|
|
4912
|
+
static ngTemplateContextGuard(_dir, _ctx) {
|
|
4394
4913
|
return true;
|
|
4395
4914
|
}
|
|
4396
4915
|
constructor() {
|
|
@@ -4406,8 +4925,8 @@ class QueryDirective {
|
|
|
4406
4925
|
scope: null,
|
|
4407
4926
|
query: null,
|
|
4408
4927
|
};
|
|
4409
|
-
this.query = input.required({ ...(ngDevMode ? { debugName: "query" } : {}), alias: 'etQuery' });
|
|
4410
|
-
this.cache = input(false, { ...(ngDevMode ? { debugName: "cache" } : {}), alias: 'etQueryCache' });
|
|
4928
|
+
this.query = input.required({ ...(ngDevMode ? { debugName: "query" } : /* istanbul ignore next */ {}), alias: 'etQuery' });
|
|
4929
|
+
this.cache = input(false, { ...(ngDevMode ? { debugName: "cache" } : /* istanbul ignore next */ {}), alias: 'etQueryCache' });
|
|
4411
4930
|
this.queryState = queryStateSignal(this.query);
|
|
4412
4931
|
inject(ViewContainerRef).createEmbeddedView(inject(TemplateRef), this._viewContext);
|
|
4413
4932
|
effect(() => {
|
|
@@ -4468,10 +4987,10 @@ class QueryDirective {
|
|
|
4468
4987
|
});
|
|
4469
4988
|
});
|
|
4470
4989
|
}
|
|
4471
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4472
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "
|
|
4990
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
4991
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.7", type: QueryDirective, isStandalone: true, selector: "[etQuery]", inputs: { query: { classPropertyName: "query", publicName: "etQuery", isSignal: true, isRequired: true, transformFunction: null }, cache: { classPropertyName: "cache", publicName: "etQueryCache", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
4473
4992
|
}
|
|
4474
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
4993
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDirective, decorators: [{
|
|
4475
4994
|
type: Directive,
|
|
4476
4995
|
args: [{
|
|
4477
4996
|
selector: '[etQuery]',
|
|
@@ -4492,10 +5011,10 @@ class QueryShortNamePipe {
|
|
|
4492
5011
|
}
|
|
4493
5012
|
return route;
|
|
4494
5013
|
}
|
|
4495
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4496
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "
|
|
5014
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryShortNamePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
5015
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: QueryShortNamePipe, isStandalone: true, name: "queryShortName" }); }
|
|
4497
5016
|
}
|
|
4498
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
5017
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryShortNamePipe, decorators: [{
|
|
4499
5018
|
type: Pipe,
|
|
4500
5019
|
args: [{ name: 'queryShortName' }]
|
|
4501
5020
|
}] });
|
|
@@ -4517,34 +5036,45 @@ class QueryDevtoolsComponent {
|
|
|
4517
5036
|
this._cdr = inject(ChangeDetectorRef);
|
|
4518
5037
|
this._destroy$ = createDestroy();
|
|
4519
5038
|
this.queryClientConfigs = inject(QUERY_CLIENT_DEVTOOLS_TOKEN);
|
|
4520
|
-
this.isOpen = signal(false,
|
|
4521
|
-
|
|
4522
|
-
this.
|
|
4523
|
-
|
|
4524
|
-
this.
|
|
4525
|
-
|
|
4526
|
-
this.
|
|
4527
|
-
|
|
4528
|
-
this.
|
|
4529
|
-
|
|
5039
|
+
this.isOpen = signal(false, /* @ts-ignore */
|
|
5040
|
+
...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
5041
|
+
this.isTranslucent = signal(false, /* @ts-ignore */
|
|
5042
|
+
...(ngDevMode ? [{ debugName: "isTranslucent" }] : /* istanbul ignore next */ []));
|
|
5043
|
+
this.snapLayout = signal('full', /* @ts-ignore */
|
|
5044
|
+
...(ngDevMode ? [{ debugName: "snapLayout" }] : /* istanbul ignore next */ []));
|
|
5045
|
+
this.queryListMode = signal('live', /* @ts-ignore */
|
|
5046
|
+
...(ngDevMode ? [{ debugName: "queryListMode" }] : /* istanbul ignore next */ []));
|
|
5047
|
+
this.viewMode = signal('query', /* @ts-ignore */
|
|
5048
|
+
...(ngDevMode ? [{ debugName: "viewMode" }] : /* istanbul ignore next */ []));
|
|
5049
|
+
this.showResponse = signal(false, /* @ts-ignore */
|
|
5050
|
+
...(ngDevMode ? [{ debugName: "showResponse" }] : /* istanbul ignore next */ []));
|
|
5051
|
+
this.showRawResponse = signal(false, /* @ts-ignore */
|
|
5052
|
+
...(ngDevMode ? [{ debugName: "showRawResponse" }] : /* istanbul ignore next */ []));
|
|
5053
|
+
this.showQueryConfig = signal(false, /* @ts-ignore */
|
|
5054
|
+
...(ngDevMode ? [{ debugName: "showQueryConfig" }] : /* istanbul ignore next */ []));
|
|
5055
|
+
this.showEntityStoreValue = signal(false, /* @ts-ignore */
|
|
5056
|
+
...(ngDevMode ? [{ debugName: "showEntityStoreValue" }] : /* istanbul ignore next */ []));
|
|
5057
|
+
this.showArguments = signal(false, /* @ts-ignore */
|
|
5058
|
+
...(ngDevMode ? [{ debugName: "showArguments" }] : /* istanbul ignore next */ []));
|
|
4530
5059
|
this.selectedClientIdCtrl = new FormControl(0);
|
|
4531
5060
|
this.selectedClientId = toSignal(this.selectedClientIdCtrl.valueChanges.pipe(startWith(this.selectedClientIdCtrl.value)));
|
|
4532
|
-
this.selectedQueryId = signal(null,
|
|
5061
|
+
this.selectedQueryId = signal(null, /* @ts-ignore */
|
|
5062
|
+
...(ngDevMode ? [{ debugName: "selectedQueryId" }] : /* istanbul ignore next */ []));
|
|
4533
5063
|
this.selectedClientConfig = computed(() => {
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
}, ...(ngDevMode ? [{ debugName: "selectedClientConfig" }] : []));
|
|
5064
|
+
const configs = this.queryClientConfigs;
|
|
5065
|
+
const index = configs.length === 1 ? 0 : (this.selectedClientId() ?? 0);
|
|
5066
|
+
const config = configs[index] ?? configs[0];
|
|
5067
|
+
if (!config) {
|
|
5068
|
+
throw new Error('QueryDevtools: no query client configs registered');
|
|
5069
|
+
}
|
|
5070
|
+
return config;
|
|
5071
|
+
}, /* @ts-ignore */
|
|
5072
|
+
...(ngDevMode ? [{ debugName: "selectedClientConfig" }] : /* istanbul ignore next */ []));
|
|
4544
5073
|
this.queryStore = computed(() => {
|
|
4545
5074
|
const config = this.selectedClientConfig();
|
|
4546
5075
|
return config.client._store;
|
|
4547
|
-
},
|
|
5076
|
+
}, /* @ts-ignore */
|
|
5077
|
+
...(ngDevMode ? [{ debugName: "queryStore" }] : /* istanbul ignore next */ []));
|
|
4548
5078
|
this.queryStore$ = toObservable(this.queryStore);
|
|
4549
5079
|
this._queries$ = new BehaviorSubject([]);
|
|
4550
5080
|
this.queries = toSignal(this._queries$);
|
|
@@ -4558,14 +5088,16 @@ class QueryDevtoolsComponent {
|
|
|
4558
5088
|
return this.queries()?.find((q) => q._id === id) ?? null;
|
|
4559
5089
|
}
|
|
4560
5090
|
return this.queryHistory$.getValue()?.find((q) => q._id === id) ?? null;
|
|
4561
|
-
},
|
|
5091
|
+
}, /* @ts-ignore */
|
|
5092
|
+
...(ngDevMode ? [{ debugName: "selectedQuery" }] : /* istanbul ignore next */ []));
|
|
4562
5093
|
this.selectedQuery$ = toObservable(this.selectedQuery);
|
|
4563
5094
|
this.selectedQueryEntityStore = computed(() => {
|
|
4564
5095
|
const query = this.selectedQuery();
|
|
4565
5096
|
if (!query)
|
|
4566
5097
|
return null;
|
|
4567
5098
|
return query.store;
|
|
4568
|
-
},
|
|
5099
|
+
}, /* @ts-ignore */
|
|
5100
|
+
...(ngDevMode ? [{ debugName: "selectedQueryEntityStore" }] : /* istanbul ignore next */ []));
|
|
4569
5101
|
this.selectedQueryEntityStore$ = toObservable(this.selectedQueryEntityStore);
|
|
4570
5102
|
this.selectedQueryEntityStoreValue$ = this.selectedQueryEntityStore$.pipe(switchMap((s) => s?._change$.pipe(startWith(''), map(() => {
|
|
4571
5103
|
if (!s._dictionary)
|
|
@@ -4603,7 +5135,8 @@ class QueryDevtoolsComponent {
|
|
|
4603
5135
|
return {
|
|
4604
5136
|
...sharedCfg,
|
|
4605
5137
|
};
|
|
4606
|
-
},
|
|
5138
|
+
}, /* @ts-ignore */
|
|
5139
|
+
...(ngDevMode ? [{ debugName: "stringifiedQueryConfig" }] : /* istanbul ignore next */ []));
|
|
4607
5140
|
this.authProvider = toSignal(toObservable(this.selectedClientConfig).pipe(switchMap((c) => c.client.authProvider$)));
|
|
4608
5141
|
this.authProviderDetails = computed(() => {
|
|
4609
5142
|
const provider = this.authProvider();
|
|
@@ -4665,7 +5198,8 @@ class QueryDevtoolsComponent {
|
|
|
4665
5198
|
config: {},
|
|
4666
5199
|
};
|
|
4667
5200
|
}
|
|
4668
|
-
},
|
|
5201
|
+
}, /* @ts-ignore */
|
|
5202
|
+
...(ngDevMode ? [{ debugName: "authProviderDetails" }] : /* istanbul ignore next */ []));
|
|
4669
5203
|
this.trackByClient = (_, { client }) => client.config.baseRoute;
|
|
4670
5204
|
this.trackByQuery = (_, { _id }) => _id;
|
|
4671
5205
|
this.selectedClientIdCtrl.valueChanges
|
|
@@ -4786,14 +5320,14 @@ class QueryDevtoolsComponent {
|
|
|
4786
5320
|
clearQueryHistory() {
|
|
4787
5321
|
this.queryHistory$.next([]);
|
|
4788
5322
|
}
|
|
4789
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
4790
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: QueryDevtoolsComponent, isStandalone: true, selector: "et-query-devtools", host: { classAttribute: "et-query-devtools" }, ngImport: i0, template: "@if (!isOpen()) {\n <button (click)=\"toggleOpen()\" class=\"et-query-devtools-open-btn et-qd-chip\">Query Devtools</button>\n}\n\n@if (isOpen()) {\n <div\n [class.et-query-devtools-container--transparent]=\"isTranslucent()\"\n class=\"et-query-devtools-container et-query-devtools-container--snap-{{ snapLayout() }}\"\n >\n <header class=\"et-qd-header\">\n <h3 class=\"et-qd-header__title\">Query Devtools</h3>\n\n <div class=\"et-qd-header__select\">\n <label for=\"et-query-client-instance\">Client instance</label>\n <select [formControl]=\"selectedClientIdCtrl\" id=\"et-query-client-instance\">\n @for (clientConfig of queryClientConfigs; track trackByClient(i, clientConfig); let i = $index) {\n <option [value]=\"i\">\n {{ clientConfig.displayName ?? clientConfig.client.config.baseRoute }}\n </option>\n }\n </select>\n </div>\n\n <button (click)=\"toggleOpen()\" class=\"et-qd-btn et-qd-btn-secondary\">Close</button>\n </header>\n\n <div class=\"et-qd-sub-header\">\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !isTranslucent() }\"\n (click)=\"toggleTranslucent()\"\n class=\"et-qd-chip et-qd-translucent-clickable\"\n >\n Translucent\n </button>\n <div class=\"et-qd-sub-header__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'full' }\"\n (click)=\"selectSnapLayout('full')\"\n class=\"et-qd-chip\"\n >\n Fullscreen\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'top' }\"\n (click)=\"selectSnapLayout('top')\"\n class=\"et-qd-chip\"\n >\n Top\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'bottom' }\"\n (click)=\"selectSnapLayout('bottom')\"\n class=\"et-qd-chip\"\n >\n Bottom\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'left' }\"\n (click)=\"selectSnapLayout('left')\"\n class=\"et-qd-chip\"\n >\n Left\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'right' }\"\n (click)=\"selectSnapLayout('right')\"\n class=\"et-qd-chip\"\n >\n Right\n </button>\n </div>\n\n @if (selectedClientConfig(); as cfg) {\n <div class=\"et-qd-client\">\n <p class=\"et-qd-client__title\">{{ cfg.displayName ?? cfg.client.config.baseRoute }}</p>\n\n <div class=\"et-qd-client__actions\">\n <button (click)=\"cfg.client._store.refreshQueriesInUse()\" class=\"et-qd-chip\">Refresh all</button>\n <button (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true })\" class=\"et-qd-chip\">\n Refresh all (ignore caches)\n </button>\n <button\n (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true, purgeUnused: true })\"\n class=\"et-qd-chip\"\n >\n Refresh all (ignore caches & purge unused)\n </button>\n </div>\n </div>\n }\n\n @if (selectedClientConfig(); as cfg) {\n <div class=\"et-qd-queries\">\n <p class=\"et-qd-queries__title\">Inspector</p>\n <div class=\"et-qd-queries__actions\">\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': queryListMode() !== 'live' || viewMode() !== 'query' }\"\n (click)=\"selectQueryListMode('live'); selectViewMode('query')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Live Queries ({{ queries()?.length ?? 0 }})\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': queryListMode() !== 'history' || viewMode() !== 'query' }\"\n (click)=\"selectQueryListMode('history'); selectViewMode('query')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Query History ({{ (queryHistory$ | async)?.length ?? 0 }}/50)\n </button>\n @if (queryListMode() === 'history' && viewMode() === 'query') {\n <div class=\"et-qd-queries__divider\"></div>\n <button (click)=\"clearQueryHistory()\" class=\"et-qd-chip et-qd-chip-bordered\">Clear history</button>\n }\n\n <div class=\"et-qd-queries__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': viewMode() !== 'authProvider' }\"\n (click)=\"selectViewMode('authProvider')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Auth Provider\n </button>\n </div>\n\n @if (viewMode() === 'query') {\n <div class=\"et-qd-queries__browser\">\n @if (queryListMode() === 'live') {\n <ul class=\"et-qd-queries__browser-list\">\n @for (query of queries(); track trackByQuery($index, query)) {\n <li>\n <button\n [class.et-qd-query--active]=\"selectedQuery()?._id === query._id\"\n (click)=\"selectQuery(query)\"\n >\n <span> {{ query._queryConfig.method }}</span>\n <span> {{ query | queryShortName }}</span>\n </button>\n </li>\n }\n </ul>\n }\n @if (queryListMode() === 'history') {\n <ul class=\"et-qd-queries__browser-list\">\n @for (query of queryHistory$ | async; track trackByQuery($index, query)) {\n <li>\n <button\n [class.et-qd-query--active]=\"selectedQuery()?._id === query._id\"\n (click)=\"selectQuery(query)\"\n >\n <span> {{ query._queryConfig.method }}</span>\n <span> {{ query | queryShortName }}</span>\n </button>\n </li>\n }\n </ul>\n }\n <div class=\"et-qd-queries__browser-detail\">\n @if (selectedQuery(); as query) {\n <p class=\"et-qd-queries__browser-detail__title\">\n {{ query._queryConfig.method }} {{ query._routeWithParams }}\n </p>\n\n <div class=\"et-qd-queries__actions\">\n <button (click)=\"query.execute()\" class=\"et-qd-chip et-qd-chip-bordered\">Execute</button>\n <button (click)=\"query.execute({ skipCache: true })\" class=\"et-qd-chip et-qd-chip-bordered\">\n Execute (ignore cache)\n </button>\n\n <button (click)=\"query.abort()\" class=\"et-qd-chip et-qd-chip-bordered\">Abort</button>\n <div class=\"et-qd-queries__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !query.isPolling }\"\n (click)=\"query.poll({ interval: 5000, takeUntil: _destroy$ })\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Poll\n </button>\n\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': query.isPolling }\"\n (click)=\"query.stopPolling()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Stop polling\n </button>\n @if (query.isPolling || query._isPollingPaused) {\n <button (click)=\"query.pausePolling()\" class=\"et-qd-chip et-qd-chip-secondary et-qd-chip-bordered\">\n Pause polling\n </button>\n <button (click)=\"query.resumePolling()\" class=\"et-qd-chip et-qd-chip-secondary et-qd-chip-bordered\">\n Resume polling\n </button>\n }\n </div>\n\n <div class=\"et-qd-list\">\n <div class=\"et-qd-list-item\">\n <p>State type</p>\n <pre>{{ query.rawState.type }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Dependents</p>\n <pre>{{ query._dependents | json }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is expired</p>\n <pre>{{ query.isExpired }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is in use</p>\n <pre>{{ query.isInUse }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Subscriber count</p>\n <pre>{{ query._subscriberCount }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is polling</p>\n <pre>{{ query.isPolling }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is polling paused</p>\n <pre>{{ query._isPollingPaused }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Enable smart polling</p>\n <pre>{{ query._enableSmartPolling }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Query store key</p>\n <pre>{{ query._queryStoreKey }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Arguments</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showArguments() }\"\n (click)=\"toggleShowArguments()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showArguments()) {\n <pre>{{ query._arguments | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>State</p>\n <p><i> Warning, this will cause a subscription if shown!</i></p>\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showResponse() }\"\n (click)=\"toggleShowResponse()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showResponse()) {\n <pre>{{ query.state$ | async | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Raw state</p>\n <p><i> The query state unaffected by entity stores</i></p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showRawResponse() }\"\n (click)=\"toggleShowRawResponse()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showRawResponse()) {\n <pre>{{ query.rawState | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Query config</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showQueryConfig() }\"\n (click)=\"toggleShowQueryConfig()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showQueryConfig()) {\n <pre>{{ stringifiedQueryConfig() | json }}</pre>\n }\n </div>\n\n @if (!!query._queryConfig.entity) {\n <div class=\"et-qd-list-item\">\n <p>Entity store value</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showEntityStoreValue() }\"\n (click)=\"toggleShowEntityStoreValue()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showEntityStoreValue()) {\n <pre>{{ selectedQueryEntityStoreValue$ | async | json }}</pre>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n } @else if (viewMode() === 'authProvider') {\n <div class=\"et-qd-auth-provider__viewer\">\n @if (authProviderDetails(); as details) {\n <div class=\"et-qd-list\">\n <div class=\"et-qd-list-item\">\n <p>Type</p>\n <pre>{{ details.type }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Header value</p>\n <pre>{{ details.header() | json }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Config</p>\n <pre>{{ details.config | json }}</pre>\n </div>\n\n @if (details.type === 'BearerAuthProvider') {\n <div class=\"et-qd-list-item\">\n <p>Tokens</p>\n <pre>{{ details.provider.tokens$ | async | json }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Should refresh on unauthorized response</p>\n <pre>{{ details.provider.shouldRefreshOnUnauthorizedResponse }}</pre>\n </div>\n\n <ng-container\n *etQuery=\"\n details.provider.currentRefreshQuery$ | async as response;\n loading as loading;\n error as error;\n query as query\n \"\n >\n <div class=\"et-qd-list-item\">\n <p>Refresh query response</p>\n\n <div>\n <button (click)=\"details.provider._refreshQuery()\" class=\"et-qd-chip et-qd-chip-bordered\">\n Refresh\n </button>\n </div>\n <pre>{{ response | json }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Refresh query loading</p>\n <pre>{{ loading }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Refresh query error</p>\n <pre>{{ error ?? 'null' }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>State</p>\n <pre>{{ query?.state$ | async | json }}</pre>\n </div>\n </ng-container>\n }\n </div>\n } @else {\n <p>No auth provider set</p>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n", styles: [":host{position:fixed;top:0;z-index:1000000;font-size:14px;color:#fff}.et-query-devtools-container{position:fixed;inset:0;background-color:#25292e;z-index:10000;overflow:auto;font-family:monospace;display:grid;grid-template-rows:auto auto auto minmax(0,1fr)}.et-query-devtools-container.et-query-devtools-container--transparent{opacity:.25;pointer-events:none}.et-query-devtools-container.et-query-devtools-container--transparent .et-query-devtools-translucent-btn{pointer-events:all}.et-query-devtools-container.et-query-devtools-container--snap-left{inset-inline-start:0;inset-block-start:0;inset-block-end:0;width:50%;border-right:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-right{inset-inline-start:unset;inset-inline-end:0;inset-block-start:0;inset-block-end:0;width:50%;border-left:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-top{inset-inline-start:0;inset-block-start:0;inset-inline-end:0;height:50%;border-bottom:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-bottom{inset-inline-start:0;inset-block-start:unset;inset-block-end:0;inset-inline-end:0;height:50%;border-top:1px solid rgb(37,41,46)}button.et-query-devtools-open-btn{position:fixed;inset-inline-end:20px;inset-block-end:0;z-index:9999;font-family:monospace;border-radius:3px 3px 0 0;line-height:14px;font-size:12px}.et-qd-header{background-color:#00ffa1;padding:12px;display:grid;grid-template-columns:1fr auto auto;gap:12px;align-items:center}.et-qd-header__title{color:#25292e;margin:0;font-size:22px}.et-qd-header__select{display:grid;background-color:#25292e;border-radius:10px;overflow:hidden}.et-qd-header__select select{width:250px;color:#fff;padding:3px 10px 2px;border:none;background-color:#25292e;font-family:monospace}.et-qd-header__select label{padding:3px 10px;color:#25292e;font-size:12px;font-weight:700;color:#fff;border-bottom:1px solid gray}.et-qd-sub-header{background-color:#00b371;padding:12px;display:flex;gap:12px;flex-wrap:wrap}.et-qd-sub-header__divider{width:1px;background-color:#25292e}.et-qd-btn{background-color:#00ffa1;color:#25292e;border:none;padding:12px;cursor:pointer;font-size:14px;font-weight:700;text-align:center;text-decoration:none;display:inline-block;width:100%;transition:all .3s ease-in-out;font-family:monospace;border-radius:10px}.et-qd-btn:hover{background-color:#00cc81}.et-qd-btn.et-qd-btn-secondary{background-color:#25292e;color:#00ffa1}.et-qd-btn.et-qd-btn-secondary:hover{background-color:#3c424a}.et-qd-chip{background-color:#00ffa1;color:#25292e;border:none;padding:4px 9px;cursor:pointer;font-size:12px;font-weight:700;text-align:center;text-decoration:none;display:inline-block;transition:all .3s ease-in-out;font-family:monospace;border-radius:10px}.et-qd-chip:hover{background-color:#00cc81}.et-qd-chip.et-qd-chip-secondary{background-color:#25292e;color:#00ffa1}.et-qd-chip.et-qd-chip-secondary:hover{background-color:#3c424a}.et-qd-chip.et-qd-chip-bordered{border:1px solid rgb(0,255,161)}.et-qd-translucent-clickable{pointer-events:all}.et-qd-client{border-bottom:1px solid rgb(59.734939759,66.1927710843,74.265060241);padding:12px}.et-qd-client__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-client__actions{display:flex;gap:12px;margin-top:12px}.et-qd-queries{padding:12px;display:grid;grid-template-rows:auto auto minmax(0,1fr)}.et-qd-queries__actions{display:flex;gap:12px;margin-top:12px;margin-bottom:16px}.et-qd-queries__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-queries__divider{width:1px;background-color:#3c424a}.et-qd-queries__browser{display:grid;grid-template-columns:1fr minmax(0,3fr);margin-left:-12px;margin-right:-12px;border-top:1px solid rgb(59.734939759,66.1927710843,74.265060241);margin-bottom:-12px}.et-qd-queries__browser-list{border-right:1px solid rgb(59.734939759,66.1927710843,74.265060241);list-style:none;padding:0;margin:0;display:grid;grid-auto-flow:row;grid-auto-rows:55px;overflow-y:auto;overflow-x:hidden}.et-qd-queries__browser-list li{margin:0}.et-qd-queries__browser-list li button{all:unset;font-family:monospace;box-sizing:border-box;width:100%;border:none;border-bottom:1px solid rgb(59.734939759,66.1927710843,74.265060241);background-color:#25292e;color:#fff;text-align:left;padding:12px;height:100%;transition:all .3s ease-in-out;cursor:pointer;display:grid;grid-template-columns:auto minmax(0,1fr);gap:2px;display:block}.et-qd-queries__browser-list li button span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.et-qd-queries__browser-list li button span:first-of-type{font-weight:700}.et-qd-queries__browser-list li button:hover,.et-qd-queries__browser-list li button.et-qd-query--active{background-color:#30363c}.et-qd-queries__browser-detail{padding:12px;overflow-y:auto;overflow-x:hidden}.et-qd-queries__browser-detail__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-auth-provider__viewer{border-top:1px solid rgb(59.734939759,66.1927710843,74.265060241);margin-left:-12px;margin-right:-12px;margin-bottom:-12px;padding:12px;overflow-y:auto;overflow-x:hidden}.et-qd-list{display:grid;gap:12px}.et-qd-list-item{display:grid}.et-qd-list-item p{font-weight:700;margin:0;padding-bottom:8px}.et-qd-list-item p+p{margin-top:-8px}.et-qd-list-item div+pre{margin-top:8px}.et-qd-list-item pre{margin:0;padding:4px;background-color:#0e1012;border-radius:4px;max-height:450px;overflow:auto}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: QueryDirective, selector: "[etQuery]", inputs: ["etQuery", "etQueryCache"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: QueryShortNamePipe, name: "queryShortName" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.ShadowDom }); }
|
|
5323
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5324
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: QueryDevtoolsComponent, isStandalone: true, selector: "et-query-devtools", host: { classAttribute: "et-query-devtools" }, ngImport: i0, template: "@if (!isOpen()) {\n <button (click)=\"toggleOpen()\" class=\"et-query-devtools-open-btn et-qd-chip\">Query Devtools</button>\n}\n\n@if (isOpen()) {\n <div\n [class.et-query-devtools-container--transparent]=\"isTranslucent()\"\n class=\"et-query-devtools-container et-query-devtools-container--snap-{{ snapLayout() }}\"\n >\n <header class=\"et-qd-header\">\n <h3 class=\"et-qd-header__title\">Query Devtools</h3>\n\n <div class=\"et-qd-header__select\">\n <label for=\"et-query-client-instance\">Client instance</label>\n <select [formControl]=\"selectedClientIdCtrl\" id=\"et-query-client-instance\">\n @for (clientConfig of queryClientConfigs; track trackByClient(i, clientConfig); let i = $index) {\n <option [value]=\"i\">\n {{ clientConfig.displayName ?? clientConfig.client.config.baseRoute }}\n </option>\n }\n </select>\n </div>\n\n <button (click)=\"toggleOpen()\" class=\"et-qd-btn et-qd-btn-secondary\">Close</button>\n </header>\n\n <div class=\"et-qd-sub-header\">\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !isTranslucent() }\"\n (click)=\"toggleTranslucent()\"\n class=\"et-qd-chip et-qd-translucent-clickable\"\n >\n Translucent\n </button>\n <div class=\"et-qd-sub-header__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'full' }\"\n (click)=\"selectSnapLayout('full')\"\n class=\"et-qd-chip\"\n >\n Fullscreen\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'top' }\"\n (click)=\"selectSnapLayout('top')\"\n class=\"et-qd-chip\"\n >\n Top\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'bottom' }\"\n (click)=\"selectSnapLayout('bottom')\"\n class=\"et-qd-chip\"\n >\n Bottom\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'left' }\"\n (click)=\"selectSnapLayout('left')\"\n class=\"et-qd-chip\"\n >\n Left\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'right' }\"\n (click)=\"selectSnapLayout('right')\"\n class=\"et-qd-chip\"\n >\n Right\n </button>\n </div>\n\n @if (selectedClientConfig(); as cfg) {\n <div class=\"et-qd-client\">\n <p class=\"et-qd-client__title\">{{ cfg.displayName ?? cfg.client.config.baseRoute }}</p>\n\n <div class=\"et-qd-client__actions\">\n <button (click)=\"cfg.client._store.refreshQueriesInUse()\" class=\"et-qd-chip\">Refresh all</button>\n <button (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true })\" class=\"et-qd-chip\">\n Refresh all (ignore caches)\n </button>\n <button\n (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true, purgeUnused: true })\"\n class=\"et-qd-chip\"\n >\n Refresh all (ignore caches & purge unused)\n </button>\n </div>\n </div>\n }\n\n @if (selectedClientConfig(); as cfg) {\n <div class=\"et-qd-queries\">\n <p class=\"et-qd-queries__title\">Inspector</p>\n <div class=\"et-qd-queries__actions\">\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': queryListMode() !== 'live' || viewMode() !== 'query' }\"\n (click)=\"selectQueryListMode('live'); selectViewMode('query')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Live Queries ({{ queries()?.length ?? 0 }})\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': queryListMode() !== 'history' || viewMode() !== 'query' }\"\n (click)=\"selectQueryListMode('history'); selectViewMode('query')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Query History ({{ (queryHistory$ | async)?.length ?? 0 }}/50)\n </button>\n @if (queryListMode() === 'history' && viewMode() === 'query') {\n <div class=\"et-qd-queries__divider\"></div>\n <button (click)=\"clearQueryHistory()\" class=\"et-qd-chip et-qd-chip-bordered\">Clear history</button>\n }\n\n <div class=\"et-qd-queries__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': viewMode() !== 'authProvider' }\"\n (click)=\"selectViewMode('authProvider')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Auth Provider\n </button>\n </div>\n\n @if (viewMode() === 'query') {\n <div class=\"et-qd-queries__browser\">\n @if (queryListMode() === 'live') {\n <ul class=\"et-qd-queries__browser-list\">\n @for (query of queries(); track trackByQuery($index, query)) {\n <li>\n <button\n [class.et-qd-query--active]=\"selectedQuery()?._id === query._id\"\n (click)=\"selectQuery(query)\"\n >\n <span> {{ query._queryConfig.method }}</span>\n <span> {{ query | queryShortName }}</span>\n </button>\n </li>\n }\n </ul>\n }\n @if (queryListMode() === 'history') {\n <ul class=\"et-qd-queries__browser-list\">\n @for (query of queryHistory$ | async; track trackByQuery($index, query)) {\n <li>\n <button\n [class.et-qd-query--active]=\"selectedQuery()?._id === query._id\"\n (click)=\"selectQuery(query)\"\n >\n <span> {{ query._queryConfig.method }}</span>\n <span> {{ query | queryShortName }}</span>\n </button>\n </li>\n }\n </ul>\n }\n <div class=\"et-qd-queries__browser-detail\">\n @if (selectedQuery(); as query) {\n <p class=\"et-qd-queries__browser-detail__title\">\n {{ query._queryConfig.method }} {{ query._routeWithParams }}\n </p>\n\n <div class=\"et-qd-queries__actions\">\n <button (click)=\"query.execute()\" class=\"et-qd-chip et-qd-chip-bordered\">Execute</button>\n <button (click)=\"query.execute({ skipCache: true })\" class=\"et-qd-chip et-qd-chip-bordered\">\n Execute (ignore cache)\n </button>\n\n <button (click)=\"query.abort()\" class=\"et-qd-chip et-qd-chip-bordered\">Abort</button>\n <div class=\"et-qd-queries__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !query.isPolling }\"\n (click)=\"query.poll({ interval: 5000, takeUntil: _destroy$ })\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Poll\n </button>\n\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': query.isPolling }\"\n (click)=\"query.stopPolling()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Stop polling\n </button>\n @if (query.isPolling || query._isPollingPaused) {\n <button (click)=\"query.pausePolling()\" class=\"et-qd-chip et-qd-chip-secondary et-qd-chip-bordered\">\n Pause polling\n </button>\n <button (click)=\"query.resumePolling()\" class=\"et-qd-chip et-qd-chip-secondary et-qd-chip-bordered\">\n Resume polling\n </button>\n }\n </div>\n\n <div class=\"et-qd-list\">\n <div class=\"et-qd-list-item\">\n <p>State type</p>\n <pre>{{ query.rawState.type }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Dependents</p>\n <pre>{{ query._dependents | json }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is expired</p>\n <pre>{{ query.isExpired }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is in use</p>\n <pre>{{ query.isInUse }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Subscriber count</p>\n <pre>{{ query._subscriberCount }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is polling</p>\n <pre>{{ query.isPolling }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is polling paused</p>\n <pre>{{ query._isPollingPaused }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Enable smart polling</p>\n <pre>{{ query._enableSmartPolling }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Query store key</p>\n <pre>{{ query._queryStoreKey }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Arguments</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showArguments() }\"\n (click)=\"toggleShowArguments()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showArguments()) {\n <pre>{{ query._arguments | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>State</p>\n <p><i> Warning, this will cause a subscription if shown!</i></p>\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showResponse() }\"\n (click)=\"toggleShowResponse()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showResponse()) {\n <pre>{{ query.state$ | async | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Raw state</p>\n <p><i> The query state unaffected by entity stores</i></p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showRawResponse() }\"\n (click)=\"toggleShowRawResponse()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showRawResponse()) {\n <pre>{{ query.rawState | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Query config</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showQueryConfig() }\"\n (click)=\"toggleShowQueryConfig()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showQueryConfig()) {\n <pre>{{ stringifiedQueryConfig() | json }}</pre>\n }\n </div>\n\n @if (!!query._queryConfig.entity) {\n <div class=\"et-qd-list-item\">\n <p>Entity store value</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showEntityStoreValue() }\"\n (click)=\"toggleShowEntityStoreValue()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showEntityStoreValue()) {\n <pre>{{ selectedQueryEntityStoreValue$ | async | json }}</pre>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n } @else if (viewMode() === 'authProvider') {\n <div class=\"et-qd-auth-provider__viewer\">\n @if (authProviderDetails(); as details) {\n <div class=\"et-qd-list\">\n <div class=\"et-qd-list-item\">\n <p>Type</p>\n <pre>{{ details.type }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Header value</p>\n <pre>{{ details.header() | json }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Config</p>\n <pre>{{ details.config | json }}</pre>\n </div>\n\n @if (details.type === 'BearerAuthProvider') {\n <div class=\"et-qd-list-item\">\n <p>Tokens</p>\n <pre>{{ details.provider.tokens$ | async | json }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Should refresh on unauthorized response</p>\n <pre>{{ details.provider.shouldRefreshOnUnauthorizedResponse }}</pre>\n </div>\n\n <ng-container\n *etQuery=\"\n details.provider.currentRefreshQuery$ | async as response;\n loading as loading;\n error as error;\n query as query\n \"\n >\n <div class=\"et-qd-list-item\">\n <p>Refresh query response</p>\n\n <div>\n <button (click)=\"details.provider._refreshQuery()\" class=\"et-qd-chip et-qd-chip-bordered\">\n Refresh\n </button>\n </div>\n <pre>{{ response | json }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Refresh query loading</p>\n <pre>{{ loading }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Refresh query error</p>\n <pre>{{ error ?? 'null' }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>State</p>\n <pre>{{ $safeNavigationMigration(query?.state$) | async | json }}</pre>\n </div>\n </ng-container>\n }\n </div>\n } @else {\n <p>No auth provider set</p>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n", styles: [":host{position:fixed;top:0;z-index:1000000;font-size:14px;color:#fff}.et-query-devtools-container{position:fixed;inset:0;background-color:#25292e;z-index:10000;overflow:auto;font-family:monospace;display:grid;grid-template-rows:auto auto auto minmax(0,1fr)}.et-query-devtools-container.et-query-devtools-container--transparent{opacity:.25;pointer-events:none}.et-query-devtools-container.et-query-devtools-container--transparent .et-query-devtools-translucent-btn{pointer-events:all}.et-query-devtools-container.et-query-devtools-container--snap-left{inset-inline-start:0;inset-block-start:0;inset-block-end:0;width:50%;border-right:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-right{inset-inline-start:unset;inset-inline-end:0;inset-block-start:0;inset-block-end:0;width:50%;border-left:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-top{inset-inline-start:0;inset-block-start:0;inset-inline-end:0;height:50%;border-bottom:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-bottom{inset-inline-start:0;inset-block-start:unset;inset-block-end:0;inset-inline-end:0;height:50%;border-top:1px solid rgb(37,41,46)}button.et-query-devtools-open-btn{position:fixed;inset-inline-end:20px;inset-block-end:0;z-index:9999;font-family:monospace;border-radius:3px 3px 0 0;line-height:14px;font-size:12px}.et-qd-header{background-color:#00ffa1;padding:12px;display:grid;grid-template-columns:1fr auto auto;gap:12px;align-items:center}.et-qd-header__title{color:#25292e;margin:0;font-size:22px}.et-qd-header__select{display:grid;background-color:#25292e;border-radius:10px;overflow:hidden}.et-qd-header__select select{width:250px;color:#fff;padding:3px 10px 2px;border:none;background-color:#25292e;font-family:monospace}.et-qd-header__select label{padding:3px 10px;color:#25292e;font-size:12px;font-weight:700;color:#fff;border-bottom:1px solid gray}.et-qd-sub-header{background-color:#00b371;padding:12px;display:flex;gap:12px;flex-wrap:wrap}.et-qd-sub-header__divider{width:1px;background-color:#25292e}.et-qd-btn{background-color:#00ffa1;color:#25292e;border:none;padding:12px;cursor:pointer;font-size:14px;font-weight:700;text-align:center;text-decoration:none;display:inline-block;width:100%;transition:all .3s ease-in-out;font-family:monospace;border-radius:10px}.et-qd-btn:hover{background-color:#00cc81}.et-qd-btn.et-qd-btn-secondary{background-color:#25292e;color:#00ffa1}.et-qd-btn.et-qd-btn-secondary:hover{background-color:#3c424a}.et-qd-chip{background-color:#00ffa1;color:#25292e;border:none;padding:4px 9px;cursor:pointer;font-size:12px;font-weight:700;text-align:center;text-decoration:none;display:inline-block;transition:all .3s ease-in-out;font-family:monospace;border-radius:10px}.et-qd-chip:hover{background-color:#00cc81}.et-qd-chip.et-qd-chip-secondary{background-color:#25292e;color:#00ffa1}.et-qd-chip.et-qd-chip-secondary:hover{background-color:#3c424a}.et-qd-chip.et-qd-chip-bordered{border:1px solid rgb(0,255,161)}.et-qd-translucent-clickable{pointer-events:all}.et-qd-client{border-bottom:1px solid rgb(59.734939759,66.1927710843,74.265060241);padding:12px}.et-qd-client__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-client__actions{display:flex;gap:12px;margin-top:12px}.et-qd-queries{padding:12px;display:grid;grid-template-rows:auto auto minmax(0,1fr)}.et-qd-queries__actions{display:flex;gap:12px;margin-top:12px;margin-bottom:16px}.et-qd-queries__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-queries__divider{width:1px;background-color:#3c424a}.et-qd-queries__browser{display:grid;grid-template-columns:1fr minmax(0,3fr);margin-left:-12px;margin-right:-12px;border-top:1px solid rgb(59.734939759,66.1927710843,74.265060241);margin-bottom:-12px}.et-qd-queries__browser-list{border-right:1px solid rgb(59.734939759,66.1927710843,74.265060241);list-style:none;padding:0;margin:0;display:grid;grid-auto-flow:row;grid-auto-rows:55px;overflow-y:auto;overflow-x:hidden}.et-qd-queries__browser-list li{margin:0}.et-qd-queries__browser-list li button{all:unset;font-family:monospace;box-sizing:border-box;width:100%;border:none;border-bottom:1px solid rgb(59.734939759,66.1927710843,74.265060241);background-color:#25292e;color:#fff;text-align:left;padding:12px;height:100%;transition:all .3s ease-in-out;cursor:pointer;display:grid;grid-template-columns:auto minmax(0,1fr);gap:2px;display:block}.et-qd-queries__browser-list li button span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.et-qd-queries__browser-list li button span:first-of-type{font-weight:700}.et-qd-queries__browser-list li button:hover,.et-qd-queries__browser-list li button.et-qd-query--active{background-color:#30363c}.et-qd-queries__browser-detail{padding:12px;overflow-y:auto;overflow-x:hidden}.et-qd-queries__browser-detail__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-auth-provider__viewer{border-top:1px solid rgb(59.734939759,66.1927710843,74.265060241);margin-left:-12px;margin-right:-12px;margin-bottom:-12px;padding:12px;overflow-y:auto;overflow-x:hidden}.et-qd-list{display:grid;gap:12px}.et-qd-list-item{display:grid}.et-qd-list-item p{font-weight:700;margin:0;padding-bottom:8px}.et-qd-list-item p+p{margin-top:-8px}.et-qd-list-item div+pre{margin-top:8px}.et-qd-list-item pre{margin:0;padding:4px;background-color:#0e1012;border-radius:4px;max-height:450px;overflow:auto}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple]):not([ngNoCva])[formControlName],select:not([multiple]):not([ngNoCva])[formControl],select:not([multiple]):not([ngNoCva])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: QueryDirective, selector: "[etQuery]", inputs: ["etQuery", "etQueryCache"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: QueryShortNamePipe, name: "queryShortName" }], encapsulation: i0.ViewEncapsulation.ShadowDom }); }
|
|
4791
5325
|
}
|
|
4792
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
5326
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: QueryDevtoolsComponent, decorators: [{
|
|
4793
5327
|
type: Component,
|
|
4794
|
-
args: [{ selector: 'et-query-devtools',
|
|
5328
|
+
args: [{ selector: 'et-query-devtools', encapsulation: ViewEncapsulation.ShadowDom, host: {
|
|
4795
5329
|
class: 'et-query-devtools',
|
|
4796
|
-
}, imports: [AsyncPipe, JsonPipe, ReactiveFormsModule, NgClass, QueryShortNamePipe, QueryDirective], template: "@if (!isOpen()) {\n <button (click)=\"toggleOpen()\" class=\"et-query-devtools-open-btn et-qd-chip\">Query Devtools</button>\n}\n\n@if (isOpen()) {\n <div\n [class.et-query-devtools-container--transparent]=\"isTranslucent()\"\n class=\"et-query-devtools-container et-query-devtools-container--snap-{{ snapLayout() }}\"\n >\n <header class=\"et-qd-header\">\n <h3 class=\"et-qd-header__title\">Query Devtools</h3>\n\n <div class=\"et-qd-header__select\">\n <label for=\"et-query-client-instance\">Client instance</label>\n <select [formControl]=\"selectedClientIdCtrl\" id=\"et-query-client-instance\">\n @for (clientConfig of queryClientConfigs; track trackByClient(i, clientConfig); let i = $index) {\n <option [value]=\"i\">\n {{ clientConfig.displayName ?? clientConfig.client.config.baseRoute }}\n </option>\n }\n </select>\n </div>\n\n <button (click)=\"toggleOpen()\" class=\"et-qd-btn et-qd-btn-secondary\">Close</button>\n </header>\n\n <div class=\"et-qd-sub-header\">\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !isTranslucent() }\"\n (click)=\"toggleTranslucent()\"\n class=\"et-qd-chip et-qd-translucent-clickable\"\n >\n Translucent\n </button>\n <div class=\"et-qd-sub-header__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'full' }\"\n (click)=\"selectSnapLayout('full')\"\n class=\"et-qd-chip\"\n >\n Fullscreen\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'top' }\"\n (click)=\"selectSnapLayout('top')\"\n class=\"et-qd-chip\"\n >\n Top\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'bottom' }\"\n (click)=\"selectSnapLayout('bottom')\"\n class=\"et-qd-chip\"\n >\n Bottom\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'left' }\"\n (click)=\"selectSnapLayout('left')\"\n class=\"et-qd-chip\"\n >\n Left\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'right' }\"\n (click)=\"selectSnapLayout('right')\"\n class=\"et-qd-chip\"\n >\n Right\n </button>\n </div>\n\n @if (selectedClientConfig(); as cfg) {\n <div class=\"et-qd-client\">\n <p class=\"et-qd-client__title\">{{ cfg.displayName ?? cfg.client.config.baseRoute }}</p>\n\n <div class=\"et-qd-client__actions\">\n <button (click)=\"cfg.client._store.refreshQueriesInUse()\" class=\"et-qd-chip\">Refresh all</button>\n <button (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true })\" class=\"et-qd-chip\">\n Refresh all (ignore caches)\n </button>\n <button\n (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true, purgeUnused: true })\"\n class=\"et-qd-chip\"\n >\n Refresh all (ignore caches & purge unused)\n </button>\n </div>\n </div>\n }\n\n @if (selectedClientConfig(); as cfg) {\n <div class=\"et-qd-queries\">\n <p class=\"et-qd-queries__title\">Inspector</p>\n <div class=\"et-qd-queries__actions\">\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': queryListMode() !== 'live' || viewMode() !== 'query' }\"\n (click)=\"selectQueryListMode('live'); selectViewMode('query')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Live Queries ({{ queries()?.length ?? 0 }})\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': queryListMode() !== 'history' || viewMode() !== 'query' }\"\n (click)=\"selectQueryListMode('history'); selectViewMode('query')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Query History ({{ (queryHistory$ | async)?.length ?? 0 }}/50)\n </button>\n @if (queryListMode() === 'history' && viewMode() === 'query') {\n <div class=\"et-qd-queries__divider\"></div>\n <button (click)=\"clearQueryHistory()\" class=\"et-qd-chip et-qd-chip-bordered\">Clear history</button>\n }\n\n <div class=\"et-qd-queries__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': viewMode() !== 'authProvider' }\"\n (click)=\"selectViewMode('authProvider')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Auth Provider\n </button>\n </div>\n\n @if (viewMode() === 'query') {\n <div class=\"et-qd-queries__browser\">\n @if (queryListMode() === 'live') {\n <ul class=\"et-qd-queries__browser-list\">\n @for (query of queries(); track trackByQuery($index, query)) {\n <li>\n <button\n [class.et-qd-query--active]=\"selectedQuery()?._id === query._id\"\n (click)=\"selectQuery(query)\"\n >\n <span> {{ query._queryConfig.method }}</span>\n <span> {{ query | queryShortName }}</span>\n </button>\n </li>\n }\n </ul>\n }\n @if (queryListMode() === 'history') {\n <ul class=\"et-qd-queries__browser-list\">\n @for (query of queryHistory$ | async; track trackByQuery($index, query)) {\n <li>\n <button\n [class.et-qd-query--active]=\"selectedQuery()?._id === query._id\"\n (click)=\"selectQuery(query)\"\n >\n <span> {{ query._queryConfig.method }}</span>\n <span> {{ query | queryShortName }}</span>\n </button>\n </li>\n }\n </ul>\n }\n <div class=\"et-qd-queries__browser-detail\">\n @if (selectedQuery(); as query) {\n <p class=\"et-qd-queries__browser-detail__title\">\n {{ query._queryConfig.method }} {{ query._routeWithParams }}\n </p>\n\n <div class=\"et-qd-queries__actions\">\n <button (click)=\"query.execute()\" class=\"et-qd-chip et-qd-chip-bordered\">Execute</button>\n <button (click)=\"query.execute({ skipCache: true })\" class=\"et-qd-chip et-qd-chip-bordered\">\n Execute (ignore cache)\n </button>\n\n <button (click)=\"query.abort()\" class=\"et-qd-chip et-qd-chip-bordered\">Abort</button>\n <div class=\"et-qd-queries__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !query.isPolling }\"\n (click)=\"query.poll({ interval: 5000, takeUntil: _destroy$ })\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Poll\n </button>\n\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': query.isPolling }\"\n (click)=\"query.stopPolling()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Stop polling\n </button>\n @if (query.isPolling || query._isPollingPaused) {\n <button (click)=\"query.pausePolling()\" class=\"et-qd-chip et-qd-chip-secondary et-qd-chip-bordered\">\n Pause polling\n </button>\n <button (click)=\"query.resumePolling()\" class=\"et-qd-chip et-qd-chip-secondary et-qd-chip-bordered\">\n Resume polling\n </button>\n }\n </div>\n\n <div class=\"et-qd-list\">\n <div class=\"et-qd-list-item\">\n <p>State type</p>\n <pre>{{ query.rawState.type }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Dependents</p>\n <pre>{{ query._dependents | json }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is expired</p>\n <pre>{{ query.isExpired }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is in use</p>\n <pre>{{ query.isInUse }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Subscriber count</p>\n <pre>{{ query._subscriberCount }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is polling</p>\n <pre>{{ query.isPolling }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is polling paused</p>\n <pre>{{ query._isPollingPaused }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Enable smart polling</p>\n <pre>{{ query._enableSmartPolling }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Query store key</p>\n <pre>{{ query._queryStoreKey }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Arguments</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showArguments() }\"\n (click)=\"toggleShowArguments()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showArguments()) {\n <pre>{{ query._arguments | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>State</p>\n <p><i> Warning, this will cause a subscription if shown!</i></p>\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showResponse() }\"\n (click)=\"toggleShowResponse()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showResponse()) {\n <pre>{{ query.state$ | async | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Raw state</p>\n <p><i> The query state unaffected by entity stores</i></p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showRawResponse() }\"\n (click)=\"toggleShowRawResponse()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showRawResponse()) {\n <pre>{{ query.rawState | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Query config</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showQueryConfig() }\"\n (click)=\"toggleShowQueryConfig()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showQueryConfig()) {\n <pre>{{ stringifiedQueryConfig() | json }}</pre>\n }\n </div>\n\n @if (!!query._queryConfig.entity) {\n <div class=\"et-qd-list-item\">\n <p>Entity store value</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showEntityStoreValue() }\"\n (click)=\"toggleShowEntityStoreValue()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showEntityStoreValue()) {\n <pre>{{ selectedQueryEntityStoreValue$ | async | json }}</pre>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n } @else if (viewMode() === 'authProvider') {\n <div class=\"et-qd-auth-provider__viewer\">\n @if (authProviderDetails(); as details) {\n <div class=\"et-qd-list\">\n <div class=\"et-qd-list-item\">\n <p>Type</p>\n <pre>{{ details.type }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Header value</p>\n <pre>{{ details.header() | json }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Config</p>\n <pre>{{ details.config | json }}</pre>\n </div>\n\n @if (details.type === 'BearerAuthProvider') {\n <div class=\"et-qd-list-item\">\n <p>Tokens</p>\n <pre>{{ details.provider.tokens$ | async | json }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Should refresh on unauthorized response</p>\n <pre>{{ details.provider.shouldRefreshOnUnauthorizedResponse }}</pre>\n </div>\n\n <ng-container\n *etQuery=\"\n details.provider.currentRefreshQuery$ | async as response;\n loading as loading;\n error as error;\n query as query\n \"\n >\n <div class=\"et-qd-list-item\">\n <p>Refresh query response</p>\n\n <div>\n <button (click)=\"details.provider._refreshQuery()\" class=\"et-qd-chip et-qd-chip-bordered\">\n Refresh\n </button>\n </div>\n <pre>{{ response | json }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Refresh query loading</p>\n <pre>{{ loading }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Refresh query error</p>\n <pre>{{ error ?? 'null' }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>State</p>\n <pre>{{ query?.state$ | async | json }}</pre>\n </div>\n </ng-container>\n }\n </div>\n } @else {\n <p>No auth provider set</p>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n", styles: [":host{position:fixed;top:0;z-index:1000000;font-size:14px;color:#fff}.et-query-devtools-container{position:fixed;inset:0;background-color:#25292e;z-index:10000;overflow:auto;font-family:monospace;display:grid;grid-template-rows:auto auto auto minmax(0,1fr)}.et-query-devtools-container.et-query-devtools-container--transparent{opacity:.25;pointer-events:none}.et-query-devtools-container.et-query-devtools-container--transparent .et-query-devtools-translucent-btn{pointer-events:all}.et-query-devtools-container.et-query-devtools-container--snap-left{inset-inline-start:0;inset-block-start:0;inset-block-end:0;width:50%;border-right:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-right{inset-inline-start:unset;inset-inline-end:0;inset-block-start:0;inset-block-end:0;width:50%;border-left:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-top{inset-inline-start:0;inset-block-start:0;inset-inline-end:0;height:50%;border-bottom:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-bottom{inset-inline-start:0;inset-block-start:unset;inset-block-end:0;inset-inline-end:0;height:50%;border-top:1px solid rgb(37,41,46)}button.et-query-devtools-open-btn{position:fixed;inset-inline-end:20px;inset-block-end:0;z-index:9999;font-family:monospace;border-radius:3px 3px 0 0;line-height:14px;font-size:12px}.et-qd-header{background-color:#00ffa1;padding:12px;display:grid;grid-template-columns:1fr auto auto;gap:12px;align-items:center}.et-qd-header__title{color:#25292e;margin:0;font-size:22px}.et-qd-header__select{display:grid;background-color:#25292e;border-radius:10px;overflow:hidden}.et-qd-header__select select{width:250px;color:#fff;padding:3px 10px 2px;border:none;background-color:#25292e;font-family:monospace}.et-qd-header__select label{padding:3px 10px;color:#25292e;font-size:12px;font-weight:700;color:#fff;border-bottom:1px solid gray}.et-qd-sub-header{background-color:#00b371;padding:12px;display:flex;gap:12px;flex-wrap:wrap}.et-qd-sub-header__divider{width:1px;background-color:#25292e}.et-qd-btn{background-color:#00ffa1;color:#25292e;border:none;padding:12px;cursor:pointer;font-size:14px;font-weight:700;text-align:center;text-decoration:none;display:inline-block;width:100%;transition:all .3s ease-in-out;font-family:monospace;border-radius:10px}.et-qd-btn:hover{background-color:#00cc81}.et-qd-btn.et-qd-btn-secondary{background-color:#25292e;color:#00ffa1}.et-qd-btn.et-qd-btn-secondary:hover{background-color:#3c424a}.et-qd-chip{background-color:#00ffa1;color:#25292e;border:none;padding:4px 9px;cursor:pointer;font-size:12px;font-weight:700;text-align:center;text-decoration:none;display:inline-block;transition:all .3s ease-in-out;font-family:monospace;border-radius:10px}.et-qd-chip:hover{background-color:#00cc81}.et-qd-chip.et-qd-chip-secondary{background-color:#25292e;color:#00ffa1}.et-qd-chip.et-qd-chip-secondary:hover{background-color:#3c424a}.et-qd-chip.et-qd-chip-bordered{border:1px solid rgb(0,255,161)}.et-qd-translucent-clickable{pointer-events:all}.et-qd-client{border-bottom:1px solid rgb(59.734939759,66.1927710843,74.265060241);padding:12px}.et-qd-client__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-client__actions{display:flex;gap:12px;margin-top:12px}.et-qd-queries{padding:12px;display:grid;grid-template-rows:auto auto minmax(0,1fr)}.et-qd-queries__actions{display:flex;gap:12px;margin-top:12px;margin-bottom:16px}.et-qd-queries__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-queries__divider{width:1px;background-color:#3c424a}.et-qd-queries__browser{display:grid;grid-template-columns:1fr minmax(0,3fr);margin-left:-12px;margin-right:-12px;border-top:1px solid rgb(59.734939759,66.1927710843,74.265060241);margin-bottom:-12px}.et-qd-queries__browser-list{border-right:1px solid rgb(59.734939759,66.1927710843,74.265060241);list-style:none;padding:0;margin:0;display:grid;grid-auto-flow:row;grid-auto-rows:55px;overflow-y:auto;overflow-x:hidden}.et-qd-queries__browser-list li{margin:0}.et-qd-queries__browser-list li button{all:unset;font-family:monospace;box-sizing:border-box;width:100%;border:none;border-bottom:1px solid rgb(59.734939759,66.1927710843,74.265060241);background-color:#25292e;color:#fff;text-align:left;padding:12px;height:100%;transition:all .3s ease-in-out;cursor:pointer;display:grid;grid-template-columns:auto minmax(0,1fr);gap:2px;display:block}.et-qd-queries__browser-list li button span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.et-qd-queries__browser-list li button span:first-of-type{font-weight:700}.et-qd-queries__browser-list li button:hover,.et-qd-queries__browser-list li button.et-qd-query--active{background-color:#30363c}.et-qd-queries__browser-detail{padding:12px;overflow-y:auto;overflow-x:hidden}.et-qd-queries__browser-detail__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-auth-provider__viewer{border-top:1px solid rgb(59.734939759,66.1927710843,74.265060241);margin-left:-12px;margin-right:-12px;margin-bottom:-12px;padding:12px;overflow-y:auto;overflow-x:hidden}.et-qd-list{display:grid;gap:12px}.et-qd-list-item{display:grid}.et-qd-list-item p{font-weight:700;margin:0;padding-bottom:8px}.et-qd-list-item p+p{margin-top:-8px}.et-qd-list-item div+pre{margin-top:8px}.et-qd-list-item pre{margin:0;padding:4px;background-color:#0e1012;border-radius:4px;max-height:450px;overflow:auto}\n"] }]
|
|
5330
|
+
}, imports: [AsyncPipe, JsonPipe, ReactiveFormsModule, NgClass, QueryShortNamePipe, QueryDirective], template: "@if (!isOpen()) {\n <button (click)=\"toggleOpen()\" class=\"et-query-devtools-open-btn et-qd-chip\">Query Devtools</button>\n}\n\n@if (isOpen()) {\n <div\n [class.et-query-devtools-container--transparent]=\"isTranslucent()\"\n class=\"et-query-devtools-container et-query-devtools-container--snap-{{ snapLayout() }}\"\n >\n <header class=\"et-qd-header\">\n <h3 class=\"et-qd-header__title\">Query Devtools</h3>\n\n <div class=\"et-qd-header__select\">\n <label for=\"et-query-client-instance\">Client instance</label>\n <select [formControl]=\"selectedClientIdCtrl\" id=\"et-query-client-instance\">\n @for (clientConfig of queryClientConfigs; track trackByClient(i, clientConfig); let i = $index) {\n <option [value]=\"i\">\n {{ clientConfig.displayName ?? clientConfig.client.config.baseRoute }}\n </option>\n }\n </select>\n </div>\n\n <button (click)=\"toggleOpen()\" class=\"et-qd-btn et-qd-btn-secondary\">Close</button>\n </header>\n\n <div class=\"et-qd-sub-header\">\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !isTranslucent() }\"\n (click)=\"toggleTranslucent()\"\n class=\"et-qd-chip et-qd-translucent-clickable\"\n >\n Translucent\n </button>\n <div class=\"et-qd-sub-header__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'full' }\"\n (click)=\"selectSnapLayout('full')\"\n class=\"et-qd-chip\"\n >\n Fullscreen\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'top' }\"\n (click)=\"selectSnapLayout('top')\"\n class=\"et-qd-chip\"\n >\n Top\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'bottom' }\"\n (click)=\"selectSnapLayout('bottom')\"\n class=\"et-qd-chip\"\n >\n Bottom\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'left' }\"\n (click)=\"selectSnapLayout('left')\"\n class=\"et-qd-chip\"\n >\n Left\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': snapLayout() !== 'right' }\"\n (click)=\"selectSnapLayout('right')\"\n class=\"et-qd-chip\"\n >\n Right\n </button>\n </div>\n\n @if (selectedClientConfig(); as cfg) {\n <div class=\"et-qd-client\">\n <p class=\"et-qd-client__title\">{{ cfg.displayName ?? cfg.client.config.baseRoute }}</p>\n\n <div class=\"et-qd-client__actions\">\n <button (click)=\"cfg.client._store.refreshQueriesInUse()\" class=\"et-qd-chip\">Refresh all</button>\n <button (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true })\" class=\"et-qd-chip\">\n Refresh all (ignore caches)\n </button>\n <button\n (click)=\"cfg.client._store.refreshQueriesInUse({ ignoreCacheValidity: true, purgeUnused: true })\"\n class=\"et-qd-chip\"\n >\n Refresh all (ignore caches & purge unused)\n </button>\n </div>\n </div>\n }\n\n @if (selectedClientConfig(); as cfg) {\n <div class=\"et-qd-queries\">\n <p class=\"et-qd-queries__title\">Inspector</p>\n <div class=\"et-qd-queries__actions\">\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': queryListMode() !== 'live' || viewMode() !== 'query' }\"\n (click)=\"selectQueryListMode('live'); selectViewMode('query')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Live Queries ({{ queries()?.length ?? 0 }})\n </button>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': queryListMode() !== 'history' || viewMode() !== 'query' }\"\n (click)=\"selectQueryListMode('history'); selectViewMode('query')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Query History ({{ (queryHistory$ | async)?.length ?? 0 }}/50)\n </button>\n @if (queryListMode() === 'history' && viewMode() === 'query') {\n <div class=\"et-qd-queries__divider\"></div>\n <button (click)=\"clearQueryHistory()\" class=\"et-qd-chip et-qd-chip-bordered\">Clear history</button>\n }\n\n <div class=\"et-qd-queries__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': viewMode() !== 'authProvider' }\"\n (click)=\"selectViewMode('authProvider')\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Auth Provider\n </button>\n </div>\n\n @if (viewMode() === 'query') {\n <div class=\"et-qd-queries__browser\">\n @if (queryListMode() === 'live') {\n <ul class=\"et-qd-queries__browser-list\">\n @for (query of queries(); track trackByQuery($index, query)) {\n <li>\n <button\n [class.et-qd-query--active]=\"selectedQuery()?._id === query._id\"\n (click)=\"selectQuery(query)\"\n >\n <span> {{ query._queryConfig.method }}</span>\n <span> {{ query | queryShortName }}</span>\n </button>\n </li>\n }\n </ul>\n }\n @if (queryListMode() === 'history') {\n <ul class=\"et-qd-queries__browser-list\">\n @for (query of queryHistory$ | async; track trackByQuery($index, query)) {\n <li>\n <button\n [class.et-qd-query--active]=\"selectedQuery()?._id === query._id\"\n (click)=\"selectQuery(query)\"\n >\n <span> {{ query._queryConfig.method }}</span>\n <span> {{ query | queryShortName }}</span>\n </button>\n </li>\n }\n </ul>\n }\n <div class=\"et-qd-queries__browser-detail\">\n @if (selectedQuery(); as query) {\n <p class=\"et-qd-queries__browser-detail__title\">\n {{ query._queryConfig.method }} {{ query._routeWithParams }}\n </p>\n\n <div class=\"et-qd-queries__actions\">\n <button (click)=\"query.execute()\" class=\"et-qd-chip et-qd-chip-bordered\">Execute</button>\n <button (click)=\"query.execute({ skipCache: true })\" class=\"et-qd-chip et-qd-chip-bordered\">\n Execute (ignore cache)\n </button>\n\n <button (click)=\"query.abort()\" class=\"et-qd-chip et-qd-chip-bordered\">Abort</button>\n <div class=\"et-qd-queries__divider\"></div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !query.isPolling }\"\n (click)=\"query.poll({ interval: 5000, takeUntil: _destroy$ })\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Poll\n </button>\n\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': query.isPolling }\"\n (click)=\"query.stopPolling()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Stop polling\n </button>\n @if (query.isPolling || query._isPollingPaused) {\n <button (click)=\"query.pausePolling()\" class=\"et-qd-chip et-qd-chip-secondary et-qd-chip-bordered\">\n Pause polling\n </button>\n <button (click)=\"query.resumePolling()\" class=\"et-qd-chip et-qd-chip-secondary et-qd-chip-bordered\">\n Resume polling\n </button>\n }\n </div>\n\n <div class=\"et-qd-list\">\n <div class=\"et-qd-list-item\">\n <p>State type</p>\n <pre>{{ query.rawState.type }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Dependents</p>\n <pre>{{ query._dependents | json }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is expired</p>\n <pre>{{ query.isExpired }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is in use</p>\n <pre>{{ query.isInUse }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Subscriber count</p>\n <pre>{{ query._subscriberCount }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is polling</p>\n <pre>{{ query.isPolling }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Is polling paused</p>\n <pre>{{ query._isPollingPaused }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Enable smart polling</p>\n <pre>{{ query._enableSmartPolling }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Query store key</p>\n <pre>{{ query._queryStoreKey }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Arguments</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showArguments() }\"\n (click)=\"toggleShowArguments()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showArguments()) {\n <pre>{{ query._arguments | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>State</p>\n <p><i> Warning, this will cause a subscription if shown!</i></p>\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showResponse() }\"\n (click)=\"toggleShowResponse()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showResponse()) {\n <pre>{{ query.state$ | async | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Raw state</p>\n <p><i> The query state unaffected by entity stores</i></p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showRawResponse() }\"\n (click)=\"toggleShowRawResponse()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showRawResponse()) {\n <pre>{{ query.rawState | json }}</pre>\n }\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Query config</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showQueryConfig() }\"\n (click)=\"toggleShowQueryConfig()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showQueryConfig()) {\n <pre>{{ stringifiedQueryConfig() | json }}</pre>\n }\n </div>\n\n @if (!!query._queryConfig.entity) {\n <div class=\"et-qd-list-item\">\n <p>Entity store value</p>\n\n <div>\n <button\n [ngClass]=\"{ 'et-qd-chip-secondary': !showEntityStoreValue() }\"\n (click)=\"toggleShowEntityStoreValue()\"\n class=\"et-qd-chip et-qd-chip-bordered\"\n >\n Toggle\n </button>\n </div>\n @if (showEntityStoreValue()) {\n <pre>{{ selectedQueryEntityStoreValue$ | async | json }}</pre>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n } @else if (viewMode() === 'authProvider') {\n <div class=\"et-qd-auth-provider__viewer\">\n @if (authProviderDetails(); as details) {\n <div class=\"et-qd-list\">\n <div class=\"et-qd-list-item\">\n <p>Type</p>\n <pre>{{ details.type }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Header value</p>\n <pre>{{ details.header() | json }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Config</p>\n <pre>{{ details.config | json }}</pre>\n </div>\n\n @if (details.type === 'BearerAuthProvider') {\n <div class=\"et-qd-list-item\">\n <p>Tokens</p>\n <pre>{{ details.provider.tokens$ | async | json }}</pre>\n </div>\n\n <div class=\"et-qd-list-item\">\n <p>Should refresh on unauthorized response</p>\n <pre>{{ details.provider.shouldRefreshOnUnauthorizedResponse }}</pre>\n </div>\n\n <ng-container\n *etQuery=\"\n details.provider.currentRefreshQuery$ | async as response;\n loading as loading;\n error as error;\n query as query\n \"\n >\n <div class=\"et-qd-list-item\">\n <p>Refresh query response</p>\n\n <div>\n <button (click)=\"details.provider._refreshQuery()\" class=\"et-qd-chip et-qd-chip-bordered\">\n Refresh\n </button>\n </div>\n <pre>{{ response | json }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Refresh query loading</p>\n <pre>{{ loading }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>Refresh query error</p>\n <pre>{{ error ?? 'null' }}</pre>\n </div>\n <div class=\"et-qd-list-item\">\n <p>State</p>\n <pre>{{ $safeNavigationMigration(query?.state$) | async | json }}</pre>\n </div>\n </ng-container>\n }\n </div>\n } @else {\n <p>No auth provider set</p>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n", styles: [":host{position:fixed;top:0;z-index:1000000;font-size:14px;color:#fff}.et-query-devtools-container{position:fixed;inset:0;background-color:#25292e;z-index:10000;overflow:auto;font-family:monospace;display:grid;grid-template-rows:auto auto auto minmax(0,1fr)}.et-query-devtools-container.et-query-devtools-container--transparent{opacity:.25;pointer-events:none}.et-query-devtools-container.et-query-devtools-container--transparent .et-query-devtools-translucent-btn{pointer-events:all}.et-query-devtools-container.et-query-devtools-container--snap-left{inset-inline-start:0;inset-block-start:0;inset-block-end:0;width:50%;border-right:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-right{inset-inline-start:unset;inset-inline-end:0;inset-block-start:0;inset-block-end:0;width:50%;border-left:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-top{inset-inline-start:0;inset-block-start:0;inset-inline-end:0;height:50%;border-bottom:1px solid rgb(37,41,46)}.et-query-devtools-container.et-query-devtools-container--snap-bottom{inset-inline-start:0;inset-block-start:unset;inset-block-end:0;inset-inline-end:0;height:50%;border-top:1px solid rgb(37,41,46)}button.et-query-devtools-open-btn{position:fixed;inset-inline-end:20px;inset-block-end:0;z-index:9999;font-family:monospace;border-radius:3px 3px 0 0;line-height:14px;font-size:12px}.et-qd-header{background-color:#00ffa1;padding:12px;display:grid;grid-template-columns:1fr auto auto;gap:12px;align-items:center}.et-qd-header__title{color:#25292e;margin:0;font-size:22px}.et-qd-header__select{display:grid;background-color:#25292e;border-radius:10px;overflow:hidden}.et-qd-header__select select{width:250px;color:#fff;padding:3px 10px 2px;border:none;background-color:#25292e;font-family:monospace}.et-qd-header__select label{padding:3px 10px;color:#25292e;font-size:12px;font-weight:700;color:#fff;border-bottom:1px solid gray}.et-qd-sub-header{background-color:#00b371;padding:12px;display:flex;gap:12px;flex-wrap:wrap}.et-qd-sub-header__divider{width:1px;background-color:#25292e}.et-qd-btn{background-color:#00ffa1;color:#25292e;border:none;padding:12px;cursor:pointer;font-size:14px;font-weight:700;text-align:center;text-decoration:none;display:inline-block;width:100%;transition:all .3s ease-in-out;font-family:monospace;border-radius:10px}.et-qd-btn:hover{background-color:#00cc81}.et-qd-btn.et-qd-btn-secondary{background-color:#25292e;color:#00ffa1}.et-qd-btn.et-qd-btn-secondary:hover{background-color:#3c424a}.et-qd-chip{background-color:#00ffa1;color:#25292e;border:none;padding:4px 9px;cursor:pointer;font-size:12px;font-weight:700;text-align:center;text-decoration:none;display:inline-block;transition:all .3s ease-in-out;font-family:monospace;border-radius:10px}.et-qd-chip:hover{background-color:#00cc81}.et-qd-chip.et-qd-chip-secondary{background-color:#25292e;color:#00ffa1}.et-qd-chip.et-qd-chip-secondary:hover{background-color:#3c424a}.et-qd-chip.et-qd-chip-bordered{border:1px solid rgb(0,255,161)}.et-qd-translucent-clickable{pointer-events:all}.et-qd-client{border-bottom:1px solid rgb(59.734939759,66.1927710843,74.265060241);padding:12px}.et-qd-client__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-client__actions{display:flex;gap:12px;margin-top:12px}.et-qd-queries{padding:12px;display:grid;grid-template-rows:auto auto minmax(0,1fr)}.et-qd-queries__actions{display:flex;gap:12px;margin-top:12px;margin-bottom:16px}.et-qd-queries__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-queries__divider{width:1px;background-color:#3c424a}.et-qd-queries__browser{display:grid;grid-template-columns:1fr minmax(0,3fr);margin-left:-12px;margin-right:-12px;border-top:1px solid rgb(59.734939759,66.1927710843,74.265060241);margin-bottom:-12px}.et-qd-queries__browser-list{border-right:1px solid rgb(59.734939759,66.1927710843,74.265060241);list-style:none;padding:0;margin:0;display:grid;grid-auto-flow:row;grid-auto-rows:55px;overflow-y:auto;overflow-x:hidden}.et-qd-queries__browser-list li{margin:0}.et-qd-queries__browser-list li button{all:unset;font-family:monospace;box-sizing:border-box;width:100%;border:none;border-bottom:1px solid rgb(59.734939759,66.1927710843,74.265060241);background-color:#25292e;color:#fff;text-align:left;padding:12px;height:100%;transition:all .3s ease-in-out;cursor:pointer;display:grid;grid-template-columns:auto minmax(0,1fr);gap:2px;display:block}.et-qd-queries__browser-list li button span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.et-qd-queries__browser-list li button span:first-of-type{font-weight:700}.et-qd-queries__browser-list li button:hover,.et-qd-queries__browser-list li button.et-qd-query--active{background-color:#30363c}.et-qd-queries__browser-detail{padding:12px;overflow-y:auto;overflow-x:hidden}.et-qd-queries__browser-detail__title{margin:0;font-size:14px;color:#fff;font-weight:700}.et-qd-auth-provider__viewer{border-top:1px solid rgb(59.734939759,66.1927710843,74.265060241);margin-left:-12px;margin-right:-12px;margin-bottom:-12px;padding:12px;overflow-y:auto;overflow-x:hidden}.et-qd-list{display:grid;gap:12px}.et-qd-list-item{display:grid}.et-qd-list-item p{font-weight:700;margin:0;padding-bottom:8px}.et-qd-list-item p+p{margin-top:-8px}.et-qd-list-item div+pre{margin-top:8px}.et-qd-list-item pre{margin:0;padding:4px;background-color:#0e1012;border-radius:4px;max-height:450px;overflow:auto}\n"] }]
|
|
4797
5331
|
}], ctorParameters: () => [] });
|
|
4798
5332
|
|
|
4799
5333
|
class EntityStoreError extends Error {
|
|
@@ -4950,10 +5484,238 @@ const removeFrom = (store, { where, id }) => {
|
|
|
4950
5484
|
.subscribe();
|
|
4951
5485
|
};
|
|
4952
5486
|
|
|
4953
|
-
|
|
5487
|
+
/** `kind` of a `ValidationError` created from a single server violation. */
|
|
5488
|
+
const SERVER_VIOLATION_ERROR_KIND = 'etServerViolation';
|
|
5489
|
+
/** `kind` of the form-level fallback error created when a failed request carries no violations. */
|
|
5490
|
+
const SERVER_ERROR_KIND = 'etServerError';
|
|
5491
|
+
/**
|
|
5492
|
+
* Extracts the violation list from a failed request in any of the shapes it may reach you:
|
|
5493
|
+
* a `QueryErrorResponse` (from `query.error()` or a settled snapshot), a raw `HttpErrorResponse`,
|
|
5494
|
+
* an already-unwrapped error body, or a plain violation array. Returns `[]` when the error
|
|
5495
|
+
* carries no violations.
|
|
5496
|
+
*/
|
|
5497
|
+
const extractFormViolations = (error) => {
|
|
5498
|
+
if (!error)
|
|
5499
|
+
return [];
|
|
5500
|
+
const body = isQueryErrorResponse(error) ? error.raw.error : error instanceof HttpErrorResponse ? error.error : error;
|
|
5501
|
+
if (isSymfonyFormViolationListError(body)) {
|
|
5502
|
+
return body.violations;
|
|
5503
|
+
}
|
|
5504
|
+
if (isSymfonyListError(body)) {
|
|
5505
|
+
return body;
|
|
5506
|
+
}
|
|
5507
|
+
return [];
|
|
5508
|
+
};
|
|
5509
|
+
/**
|
|
5510
|
+
* Maps a failed request's violations onto a signal-forms field tree as validation errors —
|
|
5511
|
+
* the bridge between an API's violation list (`@ethlete/types` `FormViolationView`) and
|
|
5512
|
+
* signal forms.
|
|
5513
|
+
*
|
|
5514
|
+
* Each violation's `propertyPath` (e.g. `items[2].name`) is resolved against `fieldTree`; a
|
|
5515
|
+
* resolved violation becomes a `ServerViolationValidationError` bound to that field, an
|
|
5516
|
+
* unresolved one becomes a form-level error (customizable via `onUnmappedViolation`). A failed
|
|
5517
|
+
* request that carries no violations at all degrades to form-level `ServerValidationError`s
|
|
5518
|
+
* built from the normalized error message — so returning this function's result from a
|
|
5519
|
+
* `submit()` action never silently succeeds on failure.
|
|
5520
|
+
*
|
|
5521
|
+
* ```ts
|
|
5522
|
+
* await submit(this.form, async (field) => {
|
|
5523
|
+
* const snapshot = await executeUntilSettled(this.createPost, { args: { body: field().value() } });
|
|
5524
|
+
* const error = snapshot.error();
|
|
5525
|
+
*
|
|
5526
|
+
* if (!error) return;
|
|
5527
|
+
*
|
|
5528
|
+
* return mapViolationsToFormErrors({ fieldTree: field, error });
|
|
5529
|
+
* });
|
|
5530
|
+
* ```
|
|
5531
|
+
*/
|
|
5532
|
+
const mapViolationsToFormErrors = (options) => {
|
|
5533
|
+
const { fieldTree, error, rewritePath, onUnmappedViolation } = options;
|
|
5534
|
+
if (error === null || error === undefined)
|
|
5535
|
+
return [];
|
|
5536
|
+
const violations = extractFormViolations(error);
|
|
5537
|
+
if (!violations.length) {
|
|
5538
|
+
// An empty violation array is an explicit "nothing to map" — only unrecognized error
|
|
5539
|
+
// shapes degrade to the form-level fallback.
|
|
5540
|
+
if (Array.isArray(error) && !error.length)
|
|
5541
|
+
return [];
|
|
5542
|
+
return createFallbackErrors(error);
|
|
5543
|
+
}
|
|
5544
|
+
const errors = [];
|
|
5545
|
+
for (const violation of violations) {
|
|
5546
|
+
const rawPath = violation.propertyPath;
|
|
5547
|
+
const path = rawPath && rewritePath ? rewritePath(rawPath, violation) : rawPath;
|
|
5548
|
+
const resolvedFieldTree = path ? resolveFieldTreePath(fieldTree, path) : null;
|
|
5549
|
+
if (resolvedFieldTree) {
|
|
5550
|
+
const mappedError = {
|
|
5551
|
+
kind: SERVER_VIOLATION_ERROR_KIND,
|
|
5552
|
+
message: violation.message,
|
|
5553
|
+
fieldTree: resolvedFieldTree,
|
|
5554
|
+
violation,
|
|
5555
|
+
};
|
|
5556
|
+
errors.push(mappedError);
|
|
5557
|
+
}
|
|
5558
|
+
else if (onUnmappedViolation) {
|
|
5559
|
+
const customError = onUnmappedViolation(violation);
|
|
5560
|
+
if (customError) {
|
|
5561
|
+
errors.push(customError);
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5564
|
+
else {
|
|
5565
|
+
const unmappedError = {
|
|
5566
|
+
kind: SERVER_VIOLATION_ERROR_KIND,
|
|
5567
|
+
message: violation.message,
|
|
5568
|
+
violation,
|
|
5569
|
+
};
|
|
5570
|
+
errors.push(unmappedError);
|
|
5571
|
+
}
|
|
5572
|
+
}
|
|
5573
|
+
return errors;
|
|
5574
|
+
};
|
|
5575
|
+
const createFallbackErrors = (error) => {
|
|
5576
|
+
const queryError = isQueryErrorResponse(error) ? error : createQueryErrorResponse(error);
|
|
5577
|
+
if (queryError.isList) {
|
|
5578
|
+
return queryError.errors.map((item) => ({ kind: SERVER_ERROR_KIND, message: item.message }));
|
|
5579
|
+
}
|
|
5580
|
+
return [{ kind: SERVER_ERROR_KIND, message: queryError.error.message }];
|
|
5581
|
+
};
|
|
5582
|
+
/** Splits a property path like `items[2].name` or `translations['de'].title` into its segments. */
|
|
5583
|
+
const parsePropertyPath = (path) => {
|
|
5584
|
+
return path
|
|
5585
|
+
.replace(/\[(['"]?)([^\]]*?)\1\]/g, '.$2')
|
|
5586
|
+
.split('.')
|
|
5587
|
+
.filter((segment) => segment.length > 0);
|
|
5588
|
+
};
|
|
5589
|
+
const resolveFieldTreePath = (fieldTree, path) => {
|
|
5590
|
+
let current = fieldTree;
|
|
5591
|
+
for (const segment of parsePropertyPath(path)) {
|
|
5592
|
+
if (current === null || (typeof current !== 'object' && typeof current !== 'function')) {
|
|
5593
|
+
return null;
|
|
5594
|
+
}
|
|
5595
|
+
current = current[segment];
|
|
5596
|
+
}
|
|
5597
|
+
// A field tree node is callable (calling it returns the field state) — anything else means the
|
|
5598
|
+
// path left the tree (e.g. an index past the array's current length).
|
|
5599
|
+
return typeof current === 'function' ? current : null;
|
|
5600
|
+
};
|
|
5601
|
+
|
|
5602
|
+
// Shared core for the query-backed async validators (`validateWithQuery` /
|
|
5603
|
+
// `validateWithV2Query`). Everything except how the query is executed — the `factory` that turns
|
|
5604
|
+
// the debounced params into a `Resource` — is identical between the v2 and v3 variants, so it is
|
|
5605
|
+
// factored here and imported by both. This module is intentionally NOT part of the `http` barrel;
|
|
5606
|
+
// it is internal plumbing consumed by direct path (the legacy variant already imports from `http`).
|
|
5607
|
+
/** Default debounce (ms) applied before a validate request runs, mirroring `selectOptionsFrom*Query`. */
|
|
5608
|
+
const DEFAULT_QUERY_VALIDATION_DEBOUNCE = 300;
|
|
5609
|
+
/**
|
|
5610
|
+
* Turns a failed validate request into field errors — the default `onError` mapping shared by both
|
|
5611
|
+
* validators. Reuses the existing `mapViolationsToFormErrors` bridge: a `422` violation list is
|
|
5612
|
+
* resolved onto the child fields by `propertyPath`, while a network / non-violation error degrades
|
|
5613
|
+
* to a non-swallowed form-level error. A caller-supplied `mapViolations` overrides only the
|
|
5614
|
+
* violation → error step (it receives the already-extracted violations).
|
|
5615
|
+
*/
|
|
5616
|
+
const mapQueryValidationError = (error, ctx, mapViolations) => {
|
|
5617
|
+
// Angular's `resource()` wraps a thrown non-`Error` (our `QueryErrorResponse` / `HttpErrorResponse`)
|
|
5618
|
+
// into an `Error` and stashes the original on `.cause`, so unwrap it before extraction.
|
|
5619
|
+
const cause = error instanceof Error && error.cause !== undefined ? error.cause : error;
|
|
5620
|
+
if (mapViolations) {
|
|
5621
|
+
return mapViolations(extractFormViolations(cause), ctx);
|
|
5622
|
+
}
|
|
5623
|
+
// `ctx.fieldTree` is the (read-only) tree of the validated field; `propertyPath`s resolve against
|
|
5624
|
+
// it. The cast bridges `ReadonlyFieldTree` → `FieldTree` — `mapViolationsToFormErrors` only walks
|
|
5625
|
+
// and reads the tree, never mutates it.
|
|
5626
|
+
return mapViolationsToFormErrors({ fieldTree: ctx.fieldTree, error: cause });
|
|
5627
|
+
};
|
|
5628
|
+
/**
|
|
5629
|
+
* Binds a query-backed async validator to `path` via `validateAsync`. Success (`204` / any `2xx`
|
|
5630
|
+
* with no violation body) reports no errors; a failed request is mapped by
|
|
5631
|
+
* {@link mapQueryValidationError}. Async validation only runs once the field's synchronous
|
|
5632
|
+
* validators pass and while `when` holds — signal forms feeds the `factory` `undefined` params when
|
|
5633
|
+
* the request shouldn't run, so the resource stays idle.
|
|
5634
|
+
*/
|
|
5635
|
+
const applyQueryAsyncValidator = (path, config) => {
|
|
5636
|
+
validateAsync(path, {
|
|
5637
|
+
params: config.params,
|
|
5638
|
+
debounce: config.debounce ?? DEFAULT_QUERY_VALIDATION_DEBOUNCE,
|
|
5639
|
+
factory: config.factory,
|
|
5640
|
+
when: config.when,
|
|
5641
|
+
onSuccess: () => null,
|
|
5642
|
+
onError: (error, ctx) => mapQueryValidationError(error, ctx, config.mapViolations),
|
|
5643
|
+
});
|
|
5644
|
+
};
|
|
5645
|
+
|
|
5646
|
+
/**
|
|
5647
|
+
* Binds a query-backed async validator to a signal-forms field, adapting a **legacy v2**
|
|
5648
|
+
* `@ethlete/query` query into what `validateAsync` consumes — the `V2QueryClient` counterpart of
|
|
5649
|
+
* {@link validateWithQuery}, so apps that haven't migrated yet can still validate against the
|
|
5650
|
+
* server. The query runs through the client (bearer auth, base route, caching, Symfony error
|
|
5651
|
+
* normalization), where a raw `httpResource` would fire unauthenticated and bypass the pipeline.
|
|
5652
|
+
*
|
|
5653
|
+
* The query is prepared and executed (debounced) as the field value changes, only after the
|
|
5654
|
+
* field's synchronous validators pass. A `204` / successful response reports no errors; a
|
|
5655
|
+
* `422 FormViolationListView` maps each violation onto its child field by `propertyPath`; a
|
|
5656
|
+
* network / other error degrades to a non-swallowed form-level error.
|
|
5657
|
+
*
|
|
5658
|
+
* Call it from a schema definition, the same place you'd call `validateAsync`:
|
|
5659
|
+
*
|
|
5660
|
+
* ```ts
|
|
5661
|
+
* const schema = schema<OpportunityEmail>((p) => {
|
|
5662
|
+
* validateWithV2Query(p, {
|
|
5663
|
+
* queryCreator: postOpportunityEmailValidate, // hubApiClient.post({ ... })
|
|
5664
|
+
* args: (ctx) => ({ pathParams: { opportunityId }, body: { ...ctx.value() } }),
|
|
5665
|
+
* });
|
|
5666
|
+
* });
|
|
5667
|
+
* ```
|
|
5668
|
+
*/
|
|
5669
|
+
const validateWithV2Query = (path, config) => {
|
|
5670
|
+
applyQueryAsyncValidator(path, {
|
|
5671
|
+
params: config.args,
|
|
5672
|
+
debounce: config.debounce,
|
|
5673
|
+
when: config.when,
|
|
5674
|
+
mapViolations: config.mapViolations,
|
|
5675
|
+
factory: (params) => resource({
|
|
5676
|
+
params: () => params(),
|
|
5677
|
+
loader: ({ params: prepareArgs, abortSignal }) => {
|
|
5678
|
+
// Prepared per request — v2 caches by the prepared args, so a fresh `prepare()` is the
|
|
5679
|
+
// legacy idiom (the sibling `selectOptionsFromV2Query` does the same via `queryComputed`).
|
|
5680
|
+
const query = config.queryCreator.prepare(prepareArgs);
|
|
5681
|
+
abortSignal.addEventListener('abort', () => query.abort());
|
|
5682
|
+
query.execute();
|
|
5683
|
+
// Resolve once the query settles. A failure is thrown so it lands in `onError`, where the
|
|
5684
|
+
// violations are mapped onto the fields; the underlying `HttpErrorResponse` is thrown so
|
|
5685
|
+
// `extractFormViolations` can unwrap the 422 body.
|
|
5686
|
+
return firstValueFrom(query.state$.pipe(filter((state) => isQueryStateSuccess(state) || isQueryStateFailure(state)), take(1))).then((state) => {
|
|
5687
|
+
if (isQueryStateFailure(state)) {
|
|
5688
|
+
throw state.error.httpErrorResponse;
|
|
5689
|
+
}
|
|
5690
|
+
return state.response;
|
|
5691
|
+
});
|
|
5692
|
+
},
|
|
5693
|
+
}),
|
|
5694
|
+
});
|
|
5695
|
+
};
|
|
5696
|
+
|
|
5697
|
+
const generateUuid = () => randomId();
|
|
5698
|
+
/** @see CreateQueryClientConfigOptions.keepUnusedFor */
|
|
5699
|
+
const DEFAULT_KEEP_UNUSED_FOR = 300_000;
|
|
5700
|
+
/**
|
|
5701
|
+
* Hard cap on entries kept without consumers, per client. Retention is opportunistic, so a runaway
|
|
5702
|
+
* count (a search-as-you-type query produces a new cache key per keystroke, and each one goes through
|
|
5703
|
+
* `unbind`) must never grow unbounded — the least recently orphaned entries are dropped first.
|
|
5704
|
+
*/
|
|
5705
|
+
const MAX_UNUSED_ENTRIES = 50;
|
|
4954
5706
|
const createQueryRepository = (config) => {
|
|
4955
5707
|
const cache = new Map();
|
|
4956
5708
|
const eventsSubject = new Subject();
|
|
5709
|
+
// Bumped on every cache mutation so the devtools cache view can react. Cheap enough to keep
|
|
5710
|
+
// unconditional — it is a single integer signal with no readers unless the devtools are open.
|
|
5711
|
+
const cacheVersion = signal(0, /* @ts-ignore */
|
|
5712
|
+
...(ngDevMode ? [{ debugName: "cacheVersion" }] : /* istanbul ignore next */ []));
|
|
5713
|
+
const bumpCacheVersion = () => cacheVersion.update((v) => v + 1);
|
|
5714
|
+
const resolveKeepUnusedFor = (creatorOptions, shouldCache) => {
|
|
5715
|
+
if (config.retentionEnabled === false || !shouldCache)
|
|
5716
|
+
return 0;
|
|
5717
|
+
return Math.max(0, creatorOptions?.keepUnusedFor ?? config.keepUnusedFor ?? DEFAULT_KEEP_UNUSED_FOR);
|
|
5718
|
+
};
|
|
4957
5719
|
const request = (options) => {
|
|
4958
5720
|
const { args, creatorOptions, runQueryOptions } = options;
|
|
4959
5721
|
const shouldCache = creatorOptions?.subtle?.useQueryRepositoryCache === false
|
|
@@ -4979,6 +5741,7 @@ const createQueryRepository = (config) => {
|
|
|
4979
5741
|
})
|
|
4980
5742
|
: false;
|
|
4981
5743
|
const trackingKey = cacheKey || generateUuid();
|
|
5744
|
+
const keepUnusedFor = resolveKeepUnusedFor(creatorOptions, shouldCache);
|
|
4982
5745
|
const previousKey = options.previousKey;
|
|
4983
5746
|
if (cacheKey !== previousKey && previousKey) {
|
|
4984
5747
|
unbind(previousKey, options.consumerDestroyRef);
|
|
@@ -4986,7 +5749,10 @@ const createQueryRepository = (config) => {
|
|
|
4986
5749
|
if (shouldCache && cacheKey) {
|
|
4987
5750
|
const cacheEntry = cache.get(cacheKey);
|
|
4988
5751
|
if (cacheEntry) {
|
|
4989
|
-
|
|
5752
|
+
// The entry may have been sitting out its `keepUnusedFor` window — a consumer binding again
|
|
5753
|
+
// makes it live, so the pending eviction must go.
|
|
5754
|
+
cancelEviction(cacheEntry);
|
|
5755
|
+
bind(cacheKey, options.consumerDestroyRef, cacheEntry.request, options.isSecure ?? false, true, keepUnusedFor);
|
|
4990
5756
|
if (!runQueryOptions?.allowCache || cacheEntry.request.isStale()) {
|
|
4991
5757
|
cacheEntry.request.execute({ allowCache: runQueryOptions?.allowCache });
|
|
4992
5758
|
}
|
|
@@ -4999,13 +5765,43 @@ const createQueryRepository = (config) => {
|
|
|
4999
5765
|
method: options.method,
|
|
5000
5766
|
dependencies: config.dependencies,
|
|
5001
5767
|
clientOptions: creatorOptions,
|
|
5768
|
+
clientHeaders: config.headers,
|
|
5002
5769
|
cacheAdapter: config.cacheAdapter,
|
|
5003
5770
|
retryFn: options.retryFn ?? config.retryFn,
|
|
5004
5771
|
});
|
|
5005
5772
|
request.execute();
|
|
5006
|
-
bind(trackingKey, options.consumerDestroyRef, request, options.isSecure ?? false, shouldCache);
|
|
5773
|
+
bind(trackingKey, options.consumerDestroyRef, request, options.isSecure ?? false, shouldCache, keepUnusedFor);
|
|
5007
5774
|
return { key: trackingKey, request };
|
|
5008
5775
|
};
|
|
5776
|
+
/** Tears an entry down for good, whether it currently has consumers or not. */
|
|
5777
|
+
const destroyEntry = (key, cacheEntry) => {
|
|
5778
|
+
clearTimeout(cacheEntry.evictTimer);
|
|
5779
|
+
cacheEntry.request.destroy();
|
|
5780
|
+
cacheEntry.eventSubscription?.unsubscribe();
|
|
5781
|
+
cache.delete(key);
|
|
5782
|
+
};
|
|
5783
|
+
const cancelEviction = (cacheEntry) => {
|
|
5784
|
+
if (cacheEntry.evictTimer === undefined)
|
|
5785
|
+
return;
|
|
5786
|
+
clearTimeout(cacheEntry.evictTimer);
|
|
5787
|
+
cacheEntry.evictTimer = undefined;
|
|
5788
|
+
cacheEntry.unusedSince = undefined;
|
|
5789
|
+
};
|
|
5790
|
+
/** Drops the least recently orphaned unused entries until the cap is respected again. */
|
|
5791
|
+
const enforceUnusedEntryLimit = () => {
|
|
5792
|
+
const unused = Array.from(cache.entries()).filter(([, entry]) => entry.consumers.size === 0);
|
|
5793
|
+
if (unused.length <= MAX_UNUSED_ENTRIES)
|
|
5794
|
+
return;
|
|
5795
|
+
unused.sort(([, a], [, b]) => (a.unusedSince ?? 0) - (b.unusedSince ?? 0));
|
|
5796
|
+
for (const [key, entry] of unused.slice(0, unused.length - MAX_UNUSED_ENTRIES)) {
|
|
5797
|
+
destroyEntry(key, entry);
|
|
5798
|
+
}
|
|
5799
|
+
};
|
|
5800
|
+
const retain = (key, cacheEntry) => {
|
|
5801
|
+
cacheEntry.unusedSince = Date.now();
|
|
5802
|
+
cacheEntry.evictTimer = setTimeout(() => evict(key), cacheEntry.keepUnusedFor);
|
|
5803
|
+
enforceUnusedEntryLimit();
|
|
5804
|
+
};
|
|
5009
5805
|
const unbind = (key, consumerDestroyRef) => {
|
|
5010
5806
|
if (key === null)
|
|
5011
5807
|
return false;
|
|
@@ -5014,24 +5810,41 @@ const createQueryRepository = (config) => {
|
|
|
5014
5810
|
return false;
|
|
5015
5811
|
cacheEntry.consumers.delete(consumerDestroyRef);
|
|
5016
5812
|
if (cacheEntry.consumers.size === 0) {
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5813
|
+
// Only data is worth keeping around: an entry that never produced a response has nothing to
|
|
5814
|
+
// hand back to a returning consumer, so it is aborted immediately as it always was.
|
|
5815
|
+
if (cacheEntry.keepUnusedFor > 0 && cacheEntry.request.response() !== null) {
|
|
5816
|
+
retain(key, cacheEntry);
|
|
5817
|
+
}
|
|
5818
|
+
else {
|
|
5819
|
+
destroyEntry(key, cacheEntry);
|
|
5820
|
+
}
|
|
5020
5821
|
}
|
|
5822
|
+
bumpCacheVersion();
|
|
5021
5823
|
return true;
|
|
5022
5824
|
};
|
|
5023
5825
|
const unbindAllSecure = () => {
|
|
5024
5826
|
for (const [key, cacheEntry] of cache.entries()) {
|
|
5025
|
-
if (cacheEntry.isSecure)
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5827
|
+
if (!cacheEntry.isSecure)
|
|
5828
|
+
continue;
|
|
5829
|
+
// Force the teardown instead of routing through `unbind`: a logged out session must not leave a
|
|
5830
|
+
// retained response body behind waiting out its `keepUnusedFor` window.
|
|
5831
|
+
cacheEntry.consumers.clear();
|
|
5832
|
+
destroyEntry(key, cacheEntry);
|
|
5833
|
+
}
|
|
5834
|
+
bumpCacheVersion();
|
|
5835
|
+
eventsSubject.next({ type: 'unbind-all-secure' });
|
|
5836
|
+
};
|
|
5837
|
+
const refreshInUse = () => {
|
|
5838
|
+
for (const cacheEntry of cache.values()) {
|
|
5839
|
+
if (cacheEntry.consumers.size === 0)
|
|
5840
|
+
continue;
|
|
5841
|
+
// Re-firing a mutation would be a side effect nobody asked for, so only reads are refreshed.
|
|
5842
|
+
if (!shouldCacheQuery(cacheEntry.request.method))
|
|
5843
|
+
continue;
|
|
5844
|
+
cacheEntry.request.execute({ force: true });
|
|
5032
5845
|
}
|
|
5033
5846
|
};
|
|
5034
|
-
const bind = (key, consumerDestroyRef, request, isSecure, allowReuse) => {
|
|
5847
|
+
const bind = (key, consumerDestroyRef, request, isSecure, allowReuse, keepUnusedFor) => {
|
|
5035
5848
|
const destroyListener = consumerDestroyRef.onDestroy(() => unbind(key, consumerDestroyRef));
|
|
5036
5849
|
const cacheEntry = cache.get(key);
|
|
5037
5850
|
if (cacheEntry && allowReuse) {
|
|
@@ -5040,26 +5853,17 @@ const createQueryRepository = (config) => {
|
|
|
5040
5853
|
else {
|
|
5041
5854
|
const consumers = new Map([]);
|
|
5042
5855
|
consumers.set(consumerDestroyRef, destroyListener);
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
if (
|
|
5048
|
-
|
|
5049
|
-
type: 'request-error',
|
|
5050
|
-
|
|
5051
|
-
key,
|
|
5052
|
-
isSecure,
|
|
5053
|
-
request,
|
|
5054
|
-
});
|
|
5856
|
+
// Drive repository events off the request's discrete, terminal event stream rather than
|
|
5857
|
+
// recombining the `error` + `response` signals via combineLatest (which could observe stale
|
|
5858
|
+
// pairings). Each settle emits exactly one terminal event.
|
|
5859
|
+
const eventSubscription = request.events$.subscribe((event) => {
|
|
5860
|
+
if (event.type === 'error') {
|
|
5861
|
+
if (event.error.raw instanceof HttpErrorResponse) {
|
|
5862
|
+
eventsSubject.next({ type: 'request-error', error: event.error.raw, key, isSecure, request });
|
|
5863
|
+
}
|
|
5055
5864
|
}
|
|
5056
|
-
else if (
|
|
5057
|
-
eventsSubject.next({
|
|
5058
|
-
type: 'request-success',
|
|
5059
|
-
key,
|
|
5060
|
-
isSecure,
|
|
5061
|
-
request,
|
|
5062
|
-
});
|
|
5865
|
+
else if (event.type === HttpEventType.Response) {
|
|
5866
|
+
eventsSubject.next({ type: 'request-success', key, isSecure, request });
|
|
5063
5867
|
}
|
|
5064
5868
|
});
|
|
5065
5869
|
cache.set(key, {
|
|
@@ -5067,14 +5871,36 @@ const createQueryRepository = (config) => {
|
|
|
5067
5871
|
request,
|
|
5068
5872
|
isSecure,
|
|
5069
5873
|
eventSubscription,
|
|
5874
|
+
keepUnusedFor,
|
|
5070
5875
|
});
|
|
5071
5876
|
}
|
|
5877
|
+
bumpCacheVersion();
|
|
5878
|
+
};
|
|
5879
|
+
const cacheEntries = () => Array.from(cache.entries()).map(([key, entry]) => ({
|
|
5880
|
+
key,
|
|
5881
|
+
isSecure: entry.isSecure,
|
|
5882
|
+
consumerCount: entry.consumers.size,
|
|
5883
|
+
isUnused: entry.consumers.size === 0,
|
|
5884
|
+
request: entry.request,
|
|
5885
|
+
}));
|
|
5886
|
+
const evict = (key) => {
|
|
5887
|
+
const entry = cache.get(key);
|
|
5888
|
+
if (!entry)
|
|
5889
|
+
return;
|
|
5890
|
+
destroyEntry(key, entry);
|
|
5891
|
+
bumpCacheVersion();
|
|
5072
5892
|
};
|
|
5073
5893
|
const repository = {
|
|
5074
5894
|
request,
|
|
5075
5895
|
unbind,
|
|
5076
5896
|
unbindAllSecure,
|
|
5897
|
+
refreshInUse,
|
|
5077
5898
|
events$: eventsSubject.asObservable(),
|
|
5899
|
+
subtle: {
|
|
5900
|
+
cacheEntries,
|
|
5901
|
+
cacheVersion: cacheVersion.asReadonly(),
|
|
5902
|
+
evict,
|
|
5903
|
+
},
|
|
5078
5904
|
};
|
|
5079
5905
|
return repository;
|
|
5080
5906
|
};
|
|
@@ -5085,10 +5911,15 @@ const createQueryClient = (options) => createRootProvider(() => {
|
|
|
5085
5911
|
const injector = inject(Injector);
|
|
5086
5912
|
const repository = createQueryRepository({
|
|
5087
5913
|
...options,
|
|
5914
|
+
// Retaining unused entries on the server would pin response bodies (and a pending timer) inside
|
|
5915
|
+
// a per-request injector for the whole window, so retention is browser only.
|
|
5916
|
+
retentionEnabled: isPlatformBrowser(inject(PLATFORM_ID)),
|
|
5088
5917
|
dependencies: { httpClient, ngErrorHandler, injector },
|
|
5089
5918
|
});
|
|
5090
5919
|
const client = {
|
|
5091
5920
|
repository,
|
|
5921
|
+
baseUrl: options.baseUrl,
|
|
5922
|
+
refreshQueriesInUse: () => repository.refreshInUse(),
|
|
5092
5923
|
};
|
|
5093
5924
|
return client;
|
|
5094
5925
|
}, {
|
|
@@ -5157,6 +5988,191 @@ const createPatchQuery = createQueryTemplate('PATCH');
|
|
|
5157
5988
|
/** A query creator that creates a secure PATCH query (requires authentication) */
|
|
5158
5989
|
const createSecurePatchQuery = createSecureQueryTemplate('PATCH');
|
|
5159
5990
|
|
|
5991
|
+
/**
|
|
5992
|
+
* Executes the query and resolves once that execution has settled (response or error received).
|
|
5993
|
+
* The returned snapshot is frozen to the awaited execution, so a later execution can't swap the
|
|
5994
|
+
* `response()` / `error()` you read from it.
|
|
5995
|
+
*
|
|
5996
|
+
* Designed for imperative flows that need a mutation's outcome in place — e.g. a signal-forms
|
|
5997
|
+
* `submit()` action mapping server violations onto the form via `mapViolationsToFormErrors`.
|
|
5998
|
+
*
|
|
5999
|
+
* The promise stays pending if the execution is cancelled (e.g. `reset()`) and the query is never
|
|
6000
|
+
* executed again — the snapshot settles with whichever execution completes next.
|
|
6001
|
+
*/
|
|
6002
|
+
const executeUntilSettled = async (query, executeArgs) => {
|
|
6003
|
+
query.execute(executeArgs);
|
|
6004
|
+
const snapshot = query.createSnapshot();
|
|
6005
|
+
await firstValueFrom(snapshot.isAlive.asObservable().pipe(filter((isAlive) => !isAlive)));
|
|
6006
|
+
return snapshot;
|
|
6007
|
+
};
|
|
6008
|
+
|
|
6009
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
6010
|
+
const createSequenceState = () => ({
|
|
6011
|
+
status: signal('idle'),
|
|
6012
|
+
running: signal(false),
|
|
6013
|
+
currentStep: signal(0),
|
|
6014
|
+
error: signal(null),
|
|
6015
|
+
failedAt: signal(null),
|
|
6016
|
+
snapshots: signal([]),
|
|
6017
|
+
stepArgs: signal([]),
|
|
6018
|
+
responses: signal([]),
|
|
6019
|
+
});
|
|
6020
|
+
const buildSequence = (steps, state, devtoolsHandle) => {
|
|
6021
|
+
const run = async () => {
|
|
6022
|
+
if (state.running())
|
|
6023
|
+
throw querySequenceAlreadyRunning();
|
|
6024
|
+
state.status.set('running');
|
|
6025
|
+
state.running.set(true);
|
|
6026
|
+
state.currentStep.set(0);
|
|
6027
|
+
state.error.set(null);
|
|
6028
|
+
state.failedAt.set(null);
|
|
6029
|
+
state.snapshots.set([]);
|
|
6030
|
+
state.stepArgs.set([]);
|
|
6031
|
+
state.responses.set([]);
|
|
6032
|
+
const snapshots = [];
|
|
6033
|
+
const stepArgs = [];
|
|
6034
|
+
const responses = [];
|
|
6035
|
+
let previousResponse = undefined;
|
|
6036
|
+
for (const [i, step] of steps.entries()) {
|
|
6037
|
+
state.currentStep.set(i + 1);
|
|
6038
|
+
const { args } = step.produceArgs(previousResponse, responses);
|
|
6039
|
+
stepArgs.push(args);
|
|
6040
|
+
state.stepArgs.set([...stepArgs]);
|
|
6041
|
+
const snapshot = await executeUntilSettled(step.query, { args });
|
|
6042
|
+
snapshots.push(snapshot);
|
|
6043
|
+
state.snapshots.set([...snapshots]);
|
|
6044
|
+
const error = snapshot.error();
|
|
6045
|
+
if (error) {
|
|
6046
|
+
state.status.set('error');
|
|
6047
|
+
state.error.set(error);
|
|
6048
|
+
state.failedAt.set(i);
|
|
6049
|
+
state.running.set(false);
|
|
6050
|
+
return { ok: false, failedAt: i, error, snapshots };
|
|
6051
|
+
}
|
|
6052
|
+
const response = snapshot.response();
|
|
6053
|
+
responses.push(response);
|
|
6054
|
+
state.responses.set([...responses]);
|
|
6055
|
+
previousResponse = response;
|
|
6056
|
+
}
|
|
6057
|
+
state.status.set('success');
|
|
6058
|
+
state.running.set(false);
|
|
6059
|
+
return { ok: true, responses: responses, snapshots };
|
|
6060
|
+
};
|
|
6061
|
+
const sequence = {
|
|
6062
|
+
then: (query, mapArgs) => buildSequence([
|
|
6063
|
+
...steps,
|
|
6064
|
+
{ query, produceArgs: (previousResponse, responses) => mapArgs(previousResponse, responses) },
|
|
6065
|
+
], state, devtoolsHandle),
|
|
6066
|
+
status: state.status.asReadonly(),
|
|
6067
|
+
running: state.running.asReadonly(),
|
|
6068
|
+
currentStep: state.currentStep.asReadonly(),
|
|
6069
|
+
total: steps.length,
|
|
6070
|
+
queries: steps.map((s) => s.query),
|
|
6071
|
+
error: state.error.asReadonly(),
|
|
6072
|
+
failedAt: state.failedAt.asReadonly(),
|
|
6073
|
+
snapshots: state.snapshots.asReadonly(),
|
|
6074
|
+
stepArgs: state.stepArgs.asReadonly(),
|
|
6075
|
+
responses: state.responses.asReadonly(),
|
|
6076
|
+
run,
|
|
6077
|
+
};
|
|
6078
|
+
if (devtoolsHandle)
|
|
6079
|
+
devtoolsHandle.current = sequence;
|
|
6080
|
+
return sequence;
|
|
6081
|
+
};
|
|
6082
|
+
/**
|
|
6083
|
+
* Starts an imperative waterfall of dependent queries. Chain the remaining steps with `.then()`
|
|
6084
|
+
* and trigger it with `.run()` — see {@link QuerySequence}.
|
|
6085
|
+
*
|
|
6086
|
+
* The seed step's `args` are produced lazily (at `run()` time), so the sequence can safely live as
|
|
6087
|
+
* a component field while still reading current signal values on each run.
|
|
6088
|
+
*
|
|
6089
|
+
* @example
|
|
6090
|
+
* ```ts
|
|
6091
|
+
* readonly checkout = querySequence(this.createOrder, () => ({ args: { body: this.order() } }))
|
|
6092
|
+
* .then(this.createPayment, (order) => ({ args: { body: { orderId: order.id } } }))
|
|
6093
|
+
* .then(this.confirmOrder, (payment, [order]) => ({
|
|
6094
|
+
* args: { pathParams: { paymentId: payment.id }, body: { orderRef: order.id } },
|
|
6095
|
+
* }));
|
|
6096
|
+
*
|
|
6097
|
+
* async submit() {
|
|
6098
|
+
* const result = await this.checkout.run();
|
|
6099
|
+
* if (!result.ok) return this.handle(result.error);
|
|
6100
|
+
* const [order, payment, confirmation] = result.responses;
|
|
6101
|
+
* }
|
|
6102
|
+
* ```
|
|
6103
|
+
*/
|
|
6104
|
+
const querySequence = (query, seedArgs) => {
|
|
6105
|
+
const devtoolsHandle = isQueryDevtoolsEnabled()
|
|
6106
|
+
? { current: null }
|
|
6107
|
+
: undefined;
|
|
6108
|
+
const seed = buildSequence([{ query, produceArgs: () => seedArgs() }], createSequenceState(), devtoolsHandle);
|
|
6109
|
+
if (devtoolsHandle) {
|
|
6110
|
+
const unregister = registerQueryDevtoolsEntry({
|
|
6111
|
+
kind: 'query-sequence',
|
|
6112
|
+
handle: devtoolsHandle,
|
|
6113
|
+
meta: {},
|
|
6114
|
+
});
|
|
6115
|
+
// Sequences have no destroy hook; tie cleanup to the creating scope when there is one.
|
|
6116
|
+
try {
|
|
6117
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
6118
|
+
}
|
|
6119
|
+
catch {
|
|
6120
|
+
// Created outside an injection context — the entry lives for the app's lifetime.
|
|
6121
|
+
}
|
|
6122
|
+
}
|
|
6123
|
+
return seed;
|
|
6124
|
+
};
|
|
6125
|
+
|
|
6126
|
+
/**
|
|
6127
|
+
* Binds a query-backed async validator to a signal-forms field, adapting an `@ethlete/query` v3
|
|
6128
|
+
* query into what `validateAsync` consumes. The query runs through the query client — so bearer
|
|
6129
|
+
* auth, base route, caching and Symfony error normalization all apply — where a raw `httpResource`
|
|
6130
|
+
* would fire unauthenticated and bypass the pipeline.
|
|
6131
|
+
*
|
|
6132
|
+
* On the current v3 `QueryClient` / `createPostQuery` creators; the `validateWithV2Query` twin
|
|
6133
|
+
* covers the legacy `V2QueryClient`. The query is created once and re-executed (debounced) as the
|
|
6134
|
+
* field value changes, only after the field's synchronous validators pass. A `204` / successful
|
|
6135
|
+
* response reports no errors; a `422 FormViolationListView` maps each violation onto its child
|
|
6136
|
+
* field by `propertyPath`; a network / other error degrades to a non-swallowed form-level error.
|
|
6137
|
+
*
|
|
6138
|
+
* Call it from a schema definition, the same place you'd call `validateAsync`:
|
|
6139
|
+
*
|
|
6140
|
+
* ```ts
|
|
6141
|
+
* const schema = schema<OpportunityEmail>((p) => {
|
|
6142
|
+
* validateWithQuery(p, {
|
|
6143
|
+
* queryCreator: postOpportunityEmailValidate,
|
|
6144
|
+
* args: (ctx) => ({ pathParams: { opportunityId }, body: { ...ctx.value() } }),
|
|
6145
|
+
* });
|
|
6146
|
+
* });
|
|
6147
|
+
* ```
|
|
6148
|
+
*/
|
|
6149
|
+
const validateWithQuery = (path, config) => {
|
|
6150
|
+
applyQueryAsyncValidator(path, {
|
|
6151
|
+
params: config.args,
|
|
6152
|
+
debounce: config.debounce,
|
|
6153
|
+
when: config.when,
|
|
6154
|
+
mapViolations: config.mapViolations,
|
|
6155
|
+
factory: (params) => {
|
|
6156
|
+
// Created once, exactly like a query stack. A POST validate route doesn't auto-execute, so
|
|
6157
|
+
// the resource loader drives it — each params change re-executes with the new args.
|
|
6158
|
+
const query = config.queryCreator();
|
|
6159
|
+
return resource({
|
|
6160
|
+
params: () => params(),
|
|
6161
|
+
loader: async ({ params: requestArgs }) => {
|
|
6162
|
+
const snapshot = await executeUntilSettled(query, { args: requestArgs });
|
|
6163
|
+
const error = snapshot.error();
|
|
6164
|
+
// A failed request (e.g. 422) is thrown so it lands in `onError`, where the violations are
|
|
6165
|
+
// mapped onto the fields. A `204` / success settles with no error and no violations.
|
|
6166
|
+
if (error) {
|
|
6167
|
+
throw error;
|
|
6168
|
+
}
|
|
6169
|
+
return snapshot.response() ?? undefined;
|
|
6170
|
+
},
|
|
6171
|
+
});
|
|
6172
|
+
},
|
|
6173
|
+
});
|
|
6174
|
+
};
|
|
6175
|
+
|
|
5160
6176
|
const setupLeaderElection = () => {
|
|
5161
6177
|
const destroyRef = inject(DestroyRef);
|
|
5162
6178
|
const channelName = 'ethlete-auth-leader';
|
|
@@ -5164,8 +6180,10 @@ const setupLeaderElection = () => {
|
|
|
5164
6180
|
const leaderTimeout = 3000;
|
|
5165
6181
|
const storageKey = 'ethlete-auth-leader-heartbeat';
|
|
5166
6182
|
const tabId = `tab-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
5167
|
-
const isLeader = signal(false,
|
|
5168
|
-
|
|
6183
|
+
const isLeader = signal(false, /* @ts-ignore */
|
|
6184
|
+
...(ngDevMode ? [{ debugName: "isLeader" }] : /* istanbul ignore next */ []));
|
|
6185
|
+
const instanceCount = signal(1, /* @ts-ignore */
|
|
6186
|
+
...(ngDevMode ? [{ debugName: "instanceCount" }] : /* istanbul ignore next */ []));
|
|
5169
6187
|
const knownTabs = new Map();
|
|
5170
6188
|
knownTabs.set(tabId, Date.now());
|
|
5171
6189
|
let channel = null;
|
|
@@ -5539,11 +6557,21 @@ const defaultExtractTokens = (response) => {
|
|
|
5539
6557
|
}
|
|
5540
6558
|
return { accessToken: response['accessToken'], refreshToken: response['refreshToken'] };
|
|
5541
6559
|
};
|
|
5542
|
-
const
|
|
6560
|
+
const deriveExecutionStateType = (builder, triggeredBy) => {
|
|
6561
|
+
if (triggeredBy === 'persistent-auth')
|
|
6562
|
+
return 'autoLogin';
|
|
6563
|
+
if (builder._type === 'tokenRefreshQuery')
|
|
6564
|
+
return 'tokenRefresh';
|
|
6565
|
+
if (triggeredBy === 'token-revocation')
|
|
6566
|
+
return 'revocation';
|
|
6567
|
+
return builder.key;
|
|
6568
|
+
};
|
|
6569
|
+
const setupBearerQueryRegistry = (builders, injector, latestExecutedQuery, latestNonInternalQuery, setTokens, executionState) => {
|
|
5543
6570
|
const queries = {};
|
|
5544
6571
|
const querySnapshots = new Map();
|
|
5545
6572
|
for (const builder of builders) {
|
|
5546
|
-
const querySnapshot = signal(null,
|
|
6573
|
+
const querySnapshot = signal(null, /* @ts-ignore */
|
|
6574
|
+
...(ngDevMode ? [{ debugName: "querySnapshot" }] : /* istanbul ignore next */ []));
|
|
5547
6575
|
querySnapshots.set(builder.key, querySnapshot);
|
|
5548
6576
|
const extractTokens = builder.config.extractTokens ?? defaultExtractTokens;
|
|
5549
6577
|
effect(() => {
|
|
@@ -5572,10 +6600,27 @@ const setupBearerQueryRegistry = (builders, injector, latestExecutedQuery, lates
|
|
|
5572
6600
|
});
|
|
5573
6601
|
query.execute({ args, options });
|
|
5574
6602
|
const snapshot = query.createSnapshot();
|
|
5575
|
-
|
|
5576
|
-
if (
|
|
5577
|
-
|
|
6603
|
+
const stateType = deriveExecutionStateType(builder, options?.triggeredBy);
|
|
6604
|
+
if (stateType !== 'revocation') {
|
|
6605
|
+
latestExecutedQuery.set({ key: builder.key, snapshot });
|
|
6606
|
+
if (!snapshot.triggeredBy()) {
|
|
6607
|
+
latestNonInternalQuery.set({ key: builder.key, snapshot });
|
|
6608
|
+
}
|
|
5578
6609
|
}
|
|
6610
|
+
executionState.set({ type: stateType, state: 'loading' });
|
|
6611
|
+
effect(() => {
|
|
6612
|
+
const response = snapshot.response();
|
|
6613
|
+
const loading = snapshot.loading();
|
|
6614
|
+
const error = snapshot.error();
|
|
6615
|
+
if (loading)
|
|
6616
|
+
return;
|
|
6617
|
+
if (error) {
|
|
6618
|
+
executionState.set({ type: stateType, state: 'error', error });
|
|
6619
|
+
}
|
|
6620
|
+
else if (response) {
|
|
6621
|
+
executionState.set({ type: stateType, state: 'success', response });
|
|
6622
|
+
}
|
|
6623
|
+
}, { injector });
|
|
5579
6624
|
querySnapshot.set(snapshot);
|
|
5580
6625
|
return snapshot;
|
|
5581
6626
|
};
|
|
@@ -5635,8 +6680,10 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
5635
6680
|
const injector = inject(Injector);
|
|
5636
6681
|
const destroyRef = inject(DestroyRef);
|
|
5637
6682
|
const queryClient = config.queryClientRef[1]();
|
|
5638
|
-
const accessToken = signal(null,
|
|
5639
|
-
|
|
6683
|
+
const accessToken = signal(null, /* @ts-ignore */
|
|
6684
|
+
...(ngDevMode ? [{ debugName: "accessToken" }] : /* istanbul ignore next */ []));
|
|
6685
|
+
const refreshToken = signal(null, /* @ts-ignore */
|
|
6686
|
+
...(ngDevMode ? [{ debugName: "refreshToken" }] : /* istanbul ignore next */ []));
|
|
5640
6687
|
const afterTokenRefresh$ = new Subject();
|
|
5641
6688
|
const bearerData = computed(() => {
|
|
5642
6689
|
const token = accessToken();
|
|
@@ -5651,20 +6698,29 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
5651
6698
|
}
|
|
5652
6699
|
return null;
|
|
5653
6700
|
}
|
|
5654
|
-
},
|
|
5655
|
-
|
|
5656
|
-
const
|
|
5657
|
-
|
|
6701
|
+
}, /* @ts-ignore */
|
|
6702
|
+
...(ngDevMode ? [{ debugName: "bearerData" }] : /* istanbul ignore next */ []));
|
|
6703
|
+
const isAuthenticated = computed(() => !!accessToken(), /* @ts-ignore */
|
|
6704
|
+
...(ngDevMode ? [{ debugName: "isAuthenticated" }] : /* istanbul ignore next */ []));
|
|
6705
|
+
const latestExecutedQuery = signal(null, /* @ts-ignore */
|
|
6706
|
+
...(ngDevMode ? [{ debugName: "latestExecutedQuery" }] : /* istanbul ignore next */ []));
|
|
6707
|
+
const latestNonInternalQuery = signal(null, /* @ts-ignore */
|
|
6708
|
+
...(ngDevMode ? [{ debugName: "latestNonInternalQuery" }] : /* istanbul ignore next */ []));
|
|
6709
|
+
const executionState = signal(null, /* @ts-ignore */
|
|
6710
|
+
...(ngDevMode ? [{ debugName: "executionState" }] : /* istanbul ignore next */ []));
|
|
5658
6711
|
const setTokens = (access, refresh) => {
|
|
5659
6712
|
accessToken.set(access);
|
|
5660
6713
|
refreshToken.set(refresh);
|
|
5661
6714
|
afterTokenRefresh$.next();
|
|
5662
6715
|
};
|
|
5663
|
-
const { queries } = setupBearerQueryRegistry(config.queries, injector, latestExecutedQuery, latestNonInternalQuery, setTokens);
|
|
6716
|
+
const { queries } = setupBearerQueryRegistry(config.queries, injector, latestExecutedQuery, latestNonInternalQuery, setTokens, executionState);
|
|
5664
6717
|
const logout = () => {
|
|
5665
6718
|
accessToken.set(null);
|
|
5666
6719
|
refreshToken.set(null);
|
|
5667
6720
|
queryClient.repository.unbindAllSecure();
|
|
6721
|
+
latestExecutedQuery.set(null);
|
|
6722
|
+
latestNonInternalQuery.set(null);
|
|
6723
|
+
executionState.set({ type: 'logout', state: 'success' });
|
|
5668
6724
|
};
|
|
5669
6725
|
const isLeader = createLeaderElection(config);
|
|
5670
6726
|
const querySetupContext = {
|
|
@@ -5692,10 +6748,11 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
5692
6748
|
leaderElection: isLeader.leaderElectionContext,
|
|
5693
6749
|
afterTokenRefresh$,
|
|
5694
6750
|
queries: queries,
|
|
6751
|
+
executionState,
|
|
5695
6752
|
};
|
|
5696
6753
|
const features = setupFeatures(config.features, featureSetupContext);
|
|
5697
6754
|
setupMultiTabSyncIfEnabled(config, accessToken, refreshToken, queryClient);
|
|
5698
|
-
|
|
6755
|
+
const provider = {
|
|
5699
6756
|
queries,
|
|
5700
6757
|
features: features,
|
|
5701
6758
|
accessToken: accessToken.asReadonly(),
|
|
@@ -5704,9 +6761,25 @@ const createBearerAuthProviderImpl = (config) => {
|
|
|
5704
6761
|
isAuthenticated,
|
|
5705
6762
|
latestExecutedQuery: latestExecutedQuery.asReadonly(),
|
|
5706
6763
|
latestNonInternalQuery: latestNonInternalQuery.asReadonly(),
|
|
6764
|
+
executionState: executionState.asReadonly(),
|
|
6765
|
+
setTokens,
|
|
5707
6766
|
logout,
|
|
5708
6767
|
afterTokenRefresh$,
|
|
5709
6768
|
};
|
|
6769
|
+
if (isQueryDevtoolsEnabled()) {
|
|
6770
|
+
const unregister = registerQueryDevtoolsEntry({
|
|
6771
|
+
kind: 'auth-provider',
|
|
6772
|
+
handle: provider,
|
|
6773
|
+
meta: {
|
|
6774
|
+
name: config.name,
|
|
6775
|
+
clientName: getQueryClientName(config.queryClientRef),
|
|
6776
|
+
clientBaseUrl: queryClient.baseUrl,
|
|
6777
|
+
repository: queryClient.repository,
|
|
6778
|
+
},
|
|
6779
|
+
});
|
|
6780
|
+
destroyRef.onDestroy(unregister);
|
|
6781
|
+
}
|
|
6782
|
+
return provider;
|
|
5710
6783
|
};
|
|
5711
6784
|
const createBearerAuthProvider = (config) => createRootProvider(() => createBearerAuthProviderImpl(config), { name: `BearerAuthProvider_${config.name}` });
|
|
5712
6785
|
|
|
@@ -5853,8 +6926,10 @@ const withInactivityLogout = (config = {}) => {
|
|
|
5853
6926
|
const activityEvents = config.activityEvents ?? ['mousedown', 'keydown', 'scroll', 'touchstart'];
|
|
5854
6927
|
const destroyRef = inject(DestroyRef);
|
|
5855
6928
|
const document = inject(DOCUMENT);
|
|
5856
|
-
const enabled = signal(true,
|
|
5857
|
-
|
|
6929
|
+
const enabled = signal(true, /* @ts-ignore */
|
|
6930
|
+
...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
|
|
6931
|
+
const lastActivityTime = signal(Date.now(), /* @ts-ignore */
|
|
6932
|
+
...(ngDevMode ? [{ debugName: "lastActivityTime" }] : /* istanbul ignore next */ []));
|
|
5858
6933
|
const resetTimer = () => {
|
|
5859
6934
|
lastActivityTime.set(Date.now());
|
|
5860
6935
|
};
|
|
@@ -5937,7 +7012,8 @@ const createPersistentAuthFeature = (config, context) => {
|
|
|
5937
7012
|
}
|
|
5938
7013
|
return config.defaultRememberMe ?? false;
|
|
5939
7014
|
};
|
|
5940
|
-
const rememberMeSignal = signal(initializeRememberMe(),
|
|
7015
|
+
const rememberMeSignal = signal(initializeRememberMe(), /* @ts-ignore */
|
|
7016
|
+
...(ngDevMode ? [{ debugName: "rememberMeSignal" }] : /* istanbul ignore next */ []));
|
|
5941
7017
|
effect(() => {
|
|
5942
7018
|
const token = refreshToken();
|
|
5943
7019
|
const rememberMe = rememberMeSignal();
|
|
@@ -5998,7 +7074,8 @@ const withTokenExpirationWarning = (config = {}) => {
|
|
|
5998
7074
|
if (typeof exp !== 'number')
|
|
5999
7075
|
return null;
|
|
6000
7076
|
return new Date(exp * 1000);
|
|
6001
|
-
},
|
|
7077
|
+
}, /* @ts-ignore */
|
|
7078
|
+
...(ngDevMode ? [{ debugName: "expiresAt" }] : /* istanbul ignore next */ []));
|
|
6002
7079
|
const expiresIn$ = combineLatest([interval(checkInterval), toObservable(context.bearerData)]).pipe(map(() => {
|
|
6003
7080
|
const expiry = expiresAt();
|
|
6004
7081
|
if (!expiry)
|
|
@@ -6029,23 +7106,27 @@ const withTokenExpirationWarning = (config = {}) => {
|
|
|
6029
7106
|
const withTokenRevocation = (config) => {
|
|
6030
7107
|
return (context) => {
|
|
6031
7108
|
const revokeOnLogout = config.revokeOnLogout ?? true;
|
|
6032
|
-
const enabled = signal(true,
|
|
7109
|
+
const enabled = signal(true, /* @ts-ignore */
|
|
7110
|
+
...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
|
|
6033
7111
|
let previousAccessToken = null;
|
|
7112
|
+
let previousRefreshToken = null;
|
|
6034
7113
|
let currentRevocationSnapshot = null;
|
|
6035
|
-
const
|
|
7114
|
+
const revokeWithTokens = (accessToken, refreshToken) => {
|
|
6036
7115
|
if (currentRevocationSnapshot?.isAlive())
|
|
6037
7116
|
return currentRevocationSnapshot;
|
|
6038
|
-
const accessToken = context.accessToken();
|
|
6039
|
-
const refreshToken = context.refreshToken();
|
|
6040
7117
|
if (!accessToken && !refreshToken) {
|
|
6041
7118
|
return null;
|
|
6042
7119
|
}
|
|
6043
7120
|
const args = config.buildArgs({ accessToken, refreshToken });
|
|
7121
|
+
context.executionState.set({ type: 'revocation', state: 'loading' });
|
|
6044
7122
|
currentRevocationSnapshot = context.queries[config.queryKey].execute(args, {
|
|
6045
7123
|
triggeredBy: 'token-revocation',
|
|
6046
7124
|
});
|
|
6047
7125
|
return currentRevocationSnapshot;
|
|
6048
7126
|
};
|
|
7127
|
+
const revoke = () => {
|
|
7128
|
+
return revokeWithTokens(context.accessToken(), context.refreshToken());
|
|
7129
|
+
};
|
|
6049
7130
|
const enable = () => {
|
|
6050
7131
|
enabled.set(true);
|
|
6051
7132
|
};
|
|
@@ -6055,10 +7136,12 @@ const withTokenRevocation = (config) => {
|
|
|
6055
7136
|
if (revokeOnLogout) {
|
|
6056
7137
|
effect(() => {
|
|
6057
7138
|
const currentToken = context.accessToken();
|
|
7139
|
+
const currentRefreshToken = context.refreshToken();
|
|
6058
7140
|
if (previousAccessToken && !currentToken && enabled()) {
|
|
6059
|
-
untracked(() =>
|
|
7141
|
+
untracked(() => revokeWithTokens(previousAccessToken, previousRefreshToken));
|
|
6060
7142
|
}
|
|
6061
7143
|
previousAccessToken = currentToken;
|
|
7144
|
+
previousRefreshToken = currentRefreshToken;
|
|
6062
7145
|
}, { injector: context.injector });
|
|
6063
7146
|
}
|
|
6064
7147
|
const instance = {
|
|
@@ -6382,10 +7465,10 @@ class ParseHttpErrorCodeToTitleDePipe {
|
|
|
6382
7465
|
constructor() {
|
|
6383
7466
|
this.transform = parseHttpErrorCodeToTitleDe;
|
|
6384
7467
|
}
|
|
6385
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
6386
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "
|
|
7468
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
7469
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, isStandalone: true, name: "parseHttpErrorCodeToTitleDe" }); }
|
|
6387
7470
|
}
|
|
6388
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
7471
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleDePipe, decorators: [{
|
|
6389
7472
|
type: Pipe,
|
|
6390
7473
|
args: [{
|
|
6391
7474
|
name: 'parseHttpErrorCodeToTitleDe',
|
|
@@ -6395,10 +7478,10 @@ class ParseHttpErrorCodeToMessageDePipe {
|
|
|
6395
7478
|
constructor() {
|
|
6396
7479
|
this.transform = parseHttpErrorCodeToMessageDe;
|
|
6397
7480
|
}
|
|
6398
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
6399
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "
|
|
7481
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
7482
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, isStandalone: true, name: "parseHttpErrorCodeToMessageDe" }); }
|
|
6400
7483
|
}
|
|
6401
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
7484
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageDePipe, decorators: [{
|
|
6402
7485
|
type: Pipe,
|
|
6403
7486
|
args: [{
|
|
6404
7487
|
name: 'parseHttpErrorCodeToMessageDe',
|
|
@@ -6582,10 +7665,10 @@ class ParseHttpErrorCodeToTitleEnPipe {
|
|
|
6582
7665
|
constructor() {
|
|
6583
7666
|
this.transform = parseHttpErrorCodeToTitleEn;
|
|
6584
7667
|
}
|
|
6585
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
6586
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "
|
|
7668
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
7669
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, isStandalone: true, name: "parseHttpErrorCodeToTitleEn" }); }
|
|
6587
7670
|
}
|
|
6588
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
7671
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToTitleEnPipe, decorators: [{
|
|
6589
7672
|
type: Pipe,
|
|
6590
7673
|
args: [{
|
|
6591
7674
|
name: 'parseHttpErrorCodeToTitleEn',
|
|
@@ -6595,10 +7678,10 @@ class ParseHttpErrorCodeToMessageEnPipe {
|
|
|
6595
7678
|
constructor() {
|
|
6596
7679
|
this.transform = parseHttpErrorCodeToMessageEn;
|
|
6597
7680
|
}
|
|
6598
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "
|
|
6599
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "
|
|
7681
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
7682
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, isStandalone: true, name: "parseHttpErrorCodeToMessageEn" }); }
|
|
6600
7683
|
}
|
|
6601
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "
|
|
7684
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: ParseHttpErrorCodeToMessageEnPipe, decorators: [{
|
|
6602
7685
|
type: Pipe,
|
|
6603
7686
|
args: [{
|
|
6604
7687
|
name: 'parseHttpErrorCodeToMessageEn',
|
|
@@ -6806,7 +7889,7 @@ class DateArrayQueryField {
|
|
|
6806
7889
|
};
|
|
6807
7890
|
}
|
|
6808
7891
|
}
|
|
6809
|
-
const IGNORED_FILTER_COUNT_FIELDS = ['page', 'skip', 'take', 'limit', 'sort', 'sortBy', 'sortOrder', 'query', 'search'];
|
|
7892
|
+
const IGNORED_FILTER_COUNT_FIELDS$1 = ['page', 'skip', 'take', 'limit', 'sort', 'sortBy', 'sortOrder', 'query', 'search'];
|
|
6810
7893
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6811
7894
|
class QueryForm {
|
|
6812
7895
|
get _formValue() {
|
|
@@ -6881,7 +7964,7 @@ class QueryForm {
|
|
|
6881
7964
|
*/
|
|
6882
7965
|
this.activeFilterCount$ = this.changes$.pipe(map(({ currentValue }) => Object.entries(currentValue)
|
|
6883
7966
|
.map(([key, value]) => {
|
|
6884
|
-
if (IGNORED_FILTER_COUNT_FIELDS.includes(key) || this._fields[key]?.data.skipInFilterCount) {
|
|
7967
|
+
if (IGNORED_FILTER_COUNT_FIELDS$1.includes(key) || this._fields[key]?.data.skipInFilterCount) {
|
|
6885
7968
|
return true;
|
|
6886
7969
|
}
|
|
6887
7970
|
return this._isDefaultValue(key, value);
|
|
@@ -7022,8 +8105,11 @@ class QueryForm {
|
|
|
7022
8105
|
continue;
|
|
7023
8106
|
const valueGotRemoved = value === ET_PROPERTY_REMOVED;
|
|
7024
8107
|
if (valueGotRemoved) {
|
|
7025
|
-
|
|
7026
|
-
|
|
8108
|
+
const defaultValue = this._getDefaultValue(key);
|
|
8109
|
+
if (!equal(field.control.value, defaultValue)) {
|
|
8110
|
+
field.control.setValue(defaultValue, { emitEvent: false });
|
|
8111
|
+
didValueChanges = true;
|
|
8112
|
+
}
|
|
7027
8113
|
continue;
|
|
7028
8114
|
}
|
|
7029
8115
|
let deserializedValue = value;
|
|
@@ -7195,6 +8281,21 @@ class QueryForm {
|
|
|
7195
8281
|
});
|
|
7196
8282
|
}
|
|
7197
8283
|
_handleFormChange(forceOverwrite = false) {
|
|
8284
|
+
// Normalize values that have a valueToQueryParamTransformFn returning null/undefined to their
|
|
8285
|
+
// field default before capturing the new value. This prevents intermediate "empty object" states
|
|
8286
|
+
// (e.g. Sort emitting { active: '', direction: '' } when cleared) from being
|
|
8287
|
+
// emitted as distinct form values.
|
|
8288
|
+
for (const [key, field] of Object.entries(this._fields)) {
|
|
8289
|
+
if (!field.data.valueToQueryParamTransformFn)
|
|
8290
|
+
continue;
|
|
8291
|
+
const rawValue = this._formValue[key];
|
|
8292
|
+
if (this._isDefaultValue(key, rawValue))
|
|
8293
|
+
continue;
|
|
8294
|
+
const serialized = field.data.valueToQueryParamTransformFn(rawValue);
|
|
8295
|
+
if (serialized !== null && serialized !== undefined)
|
|
8296
|
+
continue;
|
|
8297
|
+
this.form.controls[key]?.setValue(this._getDefaultValue(key), { emitEvent: false });
|
|
8298
|
+
}
|
|
7198
8299
|
const currentVal = clone(this._currentFormValue$.value);
|
|
7199
8300
|
const newVal = clone(this._formValue);
|
|
7200
8301
|
if (equal(currentVal, newVal)) {
|
|
@@ -7229,6 +8330,433 @@ class QueryForm {
|
|
|
7229
8330
|
}
|
|
7230
8331
|
}
|
|
7231
8332
|
|
|
8333
|
+
/** URL sentinel for an explicit `null` value (a bare empty param would be ambiguous). */
|
|
8334
|
+
const ET_NULL_VALUE = 'ET_NULL__';
|
|
8335
|
+
/**
|
|
8336
|
+
* Fields excluded from `activeFilterCount` by default — pagination, sorting and
|
|
8337
|
+
* search are navigation state, not filters.
|
|
8338
|
+
*/
|
|
8339
|
+
const IGNORED_FILTER_COUNT_FIELDS = [
|
|
8340
|
+
'page',
|
|
8341
|
+
'skip',
|
|
8342
|
+
'take',
|
|
8343
|
+
'limit',
|
|
8344
|
+
'sort',
|
|
8345
|
+
'sortBy',
|
|
8346
|
+
'sortOrder',
|
|
8347
|
+
'query',
|
|
8348
|
+
'search',
|
|
8349
|
+
];
|
|
8350
|
+
const resolveDefault = (def) => {
|
|
8351
|
+
const { defaultValue } = def;
|
|
8352
|
+
return typeof defaultValue === 'function' ? defaultValue() : (defaultValue ?? null);
|
|
8353
|
+
};
|
|
8354
|
+
const buildDefaults = (fields) => {
|
|
8355
|
+
const out = {};
|
|
8356
|
+
for (const [key, def] of Object.entries(fields)) {
|
|
8357
|
+
out[key] = resolveDefault(def);
|
|
8358
|
+
}
|
|
8359
|
+
return out;
|
|
8360
|
+
};
|
|
8361
|
+
const changedKeysBetween = (previous, current) => Object.keys(current).filter((key) => !equal(previous?.[key], current[key]));
|
|
8362
|
+
/** Best-effort URL string → value coercion, mirroring the auto-transform of the legacy QueryForm. */
|
|
8363
|
+
const autoCoerce = (raw, defaultValue) => {
|
|
8364
|
+
if (raw === ET_NULL_VALUE)
|
|
8365
|
+
return null;
|
|
8366
|
+
if (typeof raw !== 'string')
|
|
8367
|
+
return raw;
|
|
8368
|
+
const defaultIsNumber = typeof defaultValue === 'number';
|
|
8369
|
+
const looksNumeric = raw.trim() === raw && !raw.startsWith('0') && !raw.endsWith('.') && !isNaN(Number(raw));
|
|
8370
|
+
if (defaultIsNumber || (looksNumeric && raw !== '')) {
|
|
8371
|
+
return transformToNumber(raw);
|
|
8372
|
+
}
|
|
8373
|
+
if (raw === 'true' || raw === 'false') {
|
|
8374
|
+
return transformToBoolean(raw);
|
|
8375
|
+
}
|
|
8376
|
+
return raw;
|
|
8377
|
+
};
|
|
8378
|
+
const computeFilterCount = (fields, value, defaults) => {
|
|
8379
|
+
let count = 0;
|
|
8380
|
+
for (const [key, def] of Object.entries(fields)) {
|
|
8381
|
+
if (IGNORED_FILTER_COUNT_FIELDS.includes(key) || def.skipInFilterCount)
|
|
8382
|
+
continue;
|
|
8383
|
+
if (!equal(value[key], defaults[key]))
|
|
8384
|
+
count++;
|
|
8385
|
+
}
|
|
8386
|
+
return count;
|
|
8387
|
+
};
|
|
8388
|
+
const createBranch = (fields, initial, injector) => {
|
|
8389
|
+
const defaults = buildDefaults(fields);
|
|
8390
|
+
const model = signal(clone(initial), /* @ts-ignore */
|
|
8391
|
+
...(ngDevMode ? [{ debugName: "model" }] : /* istanbul ignore next */ []));
|
|
8392
|
+
const tree = form(model, { injector });
|
|
8393
|
+
return {
|
|
8394
|
+
fields: tree,
|
|
8395
|
+
value: model.asReadonly(),
|
|
8396
|
+
activeFilterCount: computed(() => computeFilterCount(fields, model(), defaults)),
|
|
8397
|
+
setValue: (value) => model.set(clone(value)),
|
|
8398
|
+
patchValue: (value) => model.update((cur) => ({ ...cur, ...value })),
|
|
8399
|
+
resetFieldToDefault: (key) => model.update((cur) => ({ ...cur, [key]: defaults[key] })),
|
|
8400
|
+
resetAllFieldsToDefault: () => model.set(clone(defaults)),
|
|
8401
|
+
};
|
|
8402
|
+
};
|
|
8403
|
+
/**
|
|
8404
|
+
* A signals-first query form: binds on-screen controls to typed fields, keeps a
|
|
8405
|
+
* debounced committed value, syncs to the URL query params, and resets dependent
|
|
8406
|
+
* fields via the `isResetBy` graph — all as signals.
|
|
8407
|
+
*
|
|
8408
|
+
* @example
|
|
8409
|
+
* const qf = createQueryForm({
|
|
8410
|
+
* fields: {
|
|
8411
|
+
* search: searchQueryField(),
|
|
8412
|
+
* sort: sortQueryField(),
|
|
8413
|
+
* page: queryField<number>({ defaultValue: 1, isResetBy: ['search', 'sort'] }),
|
|
8414
|
+
* },
|
|
8415
|
+
* }).observe();
|
|
8416
|
+
* // template: <input etInput [formField]="qf.fields.search" />
|
|
8417
|
+
*/
|
|
8418
|
+
class QueryFormSignals {
|
|
8419
|
+
constructor(config) {
|
|
8420
|
+
this._router = inject(Router);
|
|
8421
|
+
this._route = inject(ActivatedRoute);
|
|
8422
|
+
this._injector = inject(Injector);
|
|
8423
|
+
this._previous = signal(null, /* @ts-ignore */
|
|
8424
|
+
...(ngDevMode ? [{ debugName: "_previous" }] : /* istanbul ignore next */ []));
|
|
8425
|
+
this._observing = signal(false, /* @ts-ignore */
|
|
8426
|
+
...(ngDevMode ? [{ debugName: "_observing" }] : /* istanbul ignore next */ []));
|
|
8427
|
+
this._queryParamChanges = injectQueryParamChanges();
|
|
8428
|
+
this._pendingTimer = null;
|
|
8429
|
+
this._skipNextResets = false;
|
|
8430
|
+
assertInInjectionContext(QueryFormSignals);
|
|
8431
|
+
this._fields = config.fields;
|
|
8432
|
+
this._prefix = config.queryParamPrefix;
|
|
8433
|
+
this._defaults = buildDefaults(config.fields);
|
|
8434
|
+
this.defaultValue = clone(this._defaults);
|
|
8435
|
+
const initial = clone(this._defaults);
|
|
8436
|
+
this._model = signal(initial, /* @ts-ignore */
|
|
8437
|
+
...(ngDevMode ? [{ debugName: "_model" }] : /* istanbul ignore next */ []));
|
|
8438
|
+
this._committed = signal(clone(initial), /* @ts-ignore */
|
|
8439
|
+
...(ngDevMode ? [{ debugName: "_committed" }] : /* istanbul ignore next */ []));
|
|
8440
|
+
this.fields = form(this._model);
|
|
8441
|
+
this.value = this._committed.asReadonly();
|
|
8442
|
+
this.previousValue = this._previous.asReadonly();
|
|
8443
|
+
this.changes = computed(() => ({ previousValue: this._previous(), currentValue: this._committed() }), /* @ts-ignore */
|
|
8444
|
+
...(ngDevMode ? [{ debugName: "changes" }] : /* istanbul ignore next */ []));
|
|
8445
|
+
this.activeFilterCount = computed(() => computeFilterCount(this._fields, this._committed(), this._defaults), /* @ts-ignore */
|
|
8446
|
+
...(ngDevMode ? [{ debugName: "activeFilterCount" }] : /* istanbul ignore next */ []));
|
|
8447
|
+
// React to live control edits: schedule a debounced commit.
|
|
8448
|
+
effect(() => {
|
|
8449
|
+
const live = this._model();
|
|
8450
|
+
untracked(() => this._onLiveChange(live));
|
|
8451
|
+
});
|
|
8452
|
+
// React to navigation (back/forward, external links): apply URL → form immediately.
|
|
8453
|
+
effect(() => {
|
|
8454
|
+
const changes = this._queryParamChanges();
|
|
8455
|
+
untracked(() => {
|
|
8456
|
+
if (!this._observing() || this._observeOptions?.syncOnNavigation === false)
|
|
8457
|
+
return;
|
|
8458
|
+
this._applyFromUrl(changes);
|
|
8459
|
+
});
|
|
8460
|
+
});
|
|
8461
|
+
inject(DestroyRef).onDestroy(() => this._cleanup());
|
|
8462
|
+
}
|
|
8463
|
+
observe(options) {
|
|
8464
|
+
if (this._observing()) {
|
|
8465
|
+
if (isDevMode()) {
|
|
8466
|
+
console.warn('QueryFormSignals.observe() was called more than once. Ignoring the extra call.');
|
|
8467
|
+
}
|
|
8468
|
+
return this;
|
|
8469
|
+
}
|
|
8470
|
+
this._observeOptions = options;
|
|
8471
|
+
this._observing.set(true);
|
|
8472
|
+
if (options?.syncOnNavigation !== false) {
|
|
8473
|
+
this._applyFromUrl(this._route.snapshot.queryParams);
|
|
8474
|
+
}
|
|
8475
|
+
// Commit whatever the model holds now (URL-restored or programmatic defaults).
|
|
8476
|
+
this._flush();
|
|
8477
|
+
return this;
|
|
8478
|
+
}
|
|
8479
|
+
unobserve() {
|
|
8480
|
+
this._cleanup();
|
|
8481
|
+
}
|
|
8482
|
+
setValue(value, options) {
|
|
8483
|
+
if (options?.skipResets)
|
|
8484
|
+
this._skipNextResets = true;
|
|
8485
|
+
this._model.set(clone(value));
|
|
8486
|
+
}
|
|
8487
|
+
patchValue(value, options) {
|
|
8488
|
+
if (options?.skipResets)
|
|
8489
|
+
this._skipNextResets = true;
|
|
8490
|
+
this._model.update((cur) => ({ ...cur, ...value }));
|
|
8491
|
+
}
|
|
8492
|
+
resetFieldToDefault(key, options) {
|
|
8493
|
+
if (options?.skipResets)
|
|
8494
|
+
this._skipNextResets = true;
|
|
8495
|
+
this._model.update((cur) => ({ ...cur, [key]: this._defaults[key] }));
|
|
8496
|
+
}
|
|
8497
|
+
resetFieldsToDefault(keys, options) {
|
|
8498
|
+
if (options?.skipResets)
|
|
8499
|
+
this._skipNextResets = true;
|
|
8500
|
+
this._model.update((cur) => {
|
|
8501
|
+
const next = { ...cur };
|
|
8502
|
+
for (const key of keys) {
|
|
8503
|
+
next[key] = this._defaults[key];
|
|
8504
|
+
}
|
|
8505
|
+
return next;
|
|
8506
|
+
});
|
|
8507
|
+
}
|
|
8508
|
+
resetAllFieldsToDefault(options) {
|
|
8509
|
+
const skip = new Set((options?.skipFields ?? []).map((key) => key));
|
|
8510
|
+
const keys = Object.keys(this._fields).filter((key) => !skip.has(key));
|
|
8511
|
+
this.resetFieldsToDefault(keys, options);
|
|
8512
|
+
}
|
|
8513
|
+
/** Create a detached editor over the same fields, seeded from the current committed value. */
|
|
8514
|
+
branch() {
|
|
8515
|
+
return createBranch(this._fields, clone(this._committed()), this._injector);
|
|
8516
|
+
}
|
|
8517
|
+
_onLiveChange(live) {
|
|
8518
|
+
if (!this._observing())
|
|
8519
|
+
return;
|
|
8520
|
+
if (equal(live, this._committed()))
|
|
8521
|
+
return;
|
|
8522
|
+
const changedKeys = changedKeysBetween(this._committed(), live);
|
|
8523
|
+
const debounceMs = this._resolveDebounce(changedKeys, live);
|
|
8524
|
+
this._clearTimer();
|
|
8525
|
+
if (debounceMs === null) {
|
|
8526
|
+
this._flush();
|
|
8527
|
+
}
|
|
8528
|
+
else {
|
|
8529
|
+
this._pendingTimer = setTimeout(() => {
|
|
8530
|
+
this._pendingTimer = null;
|
|
8531
|
+
this._flush();
|
|
8532
|
+
}, debounceMs);
|
|
8533
|
+
}
|
|
8534
|
+
}
|
|
8535
|
+
/** Shortest-debounce-wins; `null` means commit immediately. */
|
|
8536
|
+
_resolveDebounce(changedKeys, live) {
|
|
8537
|
+
if (!changedKeys.length)
|
|
8538
|
+
return null;
|
|
8539
|
+
const times = [];
|
|
8540
|
+
for (const key of changedKeys) {
|
|
8541
|
+
const def = this._fields[key];
|
|
8542
|
+
if (!def)
|
|
8543
|
+
return null;
|
|
8544
|
+
if (def.disableDebounceIfFalsy && !live[key])
|
|
8545
|
+
return null;
|
|
8546
|
+
if (def.debounce === undefined)
|
|
8547
|
+
return null;
|
|
8548
|
+
times.push(def.debounce);
|
|
8549
|
+
}
|
|
8550
|
+
return Math.min(...times);
|
|
8551
|
+
}
|
|
8552
|
+
_flush() {
|
|
8553
|
+
this._clearTimer();
|
|
8554
|
+
const live = this._model();
|
|
8555
|
+
const previous = this._committed();
|
|
8556
|
+
if (equal(live, previous)) {
|
|
8557
|
+
this._skipNextResets = false;
|
|
8558
|
+
return;
|
|
8559
|
+
}
|
|
8560
|
+
const changedKeys = changedKeysBetween(previous, live);
|
|
8561
|
+
const next = this._skipNextResets ? { ...live } : this._applyResets(live, changedKeys);
|
|
8562
|
+
this._skipNextResets = false;
|
|
8563
|
+
this._previous.set(clone(previous));
|
|
8564
|
+
this._committed.set(clone(next));
|
|
8565
|
+
// Reflect any reset overrides back into the bound controls. The effect re-runs
|
|
8566
|
+
// but no-ops because the model now equals the committed value.
|
|
8567
|
+
if (!equal(next, live)) {
|
|
8568
|
+
this._model.set(clone(next));
|
|
8569
|
+
}
|
|
8570
|
+
if (this._observing() && this._observeOptions?.writeToQueryParams !== false) {
|
|
8571
|
+
this._writeToUrl(next);
|
|
8572
|
+
}
|
|
8573
|
+
}
|
|
8574
|
+
_applyResets(live, changedKeys) {
|
|
8575
|
+
const next = { ...live };
|
|
8576
|
+
for (const [key, def] of Object.entries(this._fields)) {
|
|
8577
|
+
const resets = def.isResetBy;
|
|
8578
|
+
if (!resets?.length)
|
|
8579
|
+
continue;
|
|
8580
|
+
const triggered = resets.some((resetKey) => {
|
|
8581
|
+
if (!(resetKey in this._fields)) {
|
|
8582
|
+
if (isDevMode()) {
|
|
8583
|
+
console.warn(`QueryFormSignals: isResetBy references unknown field "${resetKey}". Is it a typo?`);
|
|
8584
|
+
}
|
|
8585
|
+
return false;
|
|
8586
|
+
}
|
|
8587
|
+
return changedKeys.includes(resetKey);
|
|
8588
|
+
});
|
|
8589
|
+
if (!triggered)
|
|
8590
|
+
continue;
|
|
8591
|
+
const defaultValue = this._defaults[key];
|
|
8592
|
+
if (!equal(next[key], defaultValue)) {
|
|
8593
|
+
next[key] = defaultValue;
|
|
8594
|
+
}
|
|
8595
|
+
}
|
|
8596
|
+
return next;
|
|
8597
|
+
}
|
|
8598
|
+
_applyFromUrl(params) {
|
|
8599
|
+
const current = { ...this._committed() };
|
|
8600
|
+
let changed = false;
|
|
8601
|
+
for (const [key, def] of Object.entries(this._fields)) {
|
|
8602
|
+
const raw = params[this._paramKey(key)];
|
|
8603
|
+
if (raw === undefined)
|
|
8604
|
+
continue;
|
|
8605
|
+
const value = raw === ET_PROPERTY_REMOVED ? this._defaults[key] : this._deserialize(def, raw);
|
|
8606
|
+
if (!equal(current[key], value)) {
|
|
8607
|
+
current[key] = value;
|
|
8608
|
+
changed = true;
|
|
8609
|
+
}
|
|
8610
|
+
}
|
|
8611
|
+
if (!changed)
|
|
8612
|
+
return;
|
|
8613
|
+
this._commitFromUrl(current);
|
|
8614
|
+
}
|
|
8615
|
+
_commitFromUrl(next) {
|
|
8616
|
+
this._clearTimer();
|
|
8617
|
+
this._previous.set(clone(this._committed()));
|
|
8618
|
+
this._committed.set(clone(next));
|
|
8619
|
+
// Feed the URL value into the live model & bound controls; the effect no-ops (model === committed).
|
|
8620
|
+
this._model.set(clone(next));
|
|
8621
|
+
}
|
|
8622
|
+
_deserialize(def, raw) {
|
|
8623
|
+
if (def.queryParamToValue)
|
|
8624
|
+
return def.queryParamToValue(raw);
|
|
8625
|
+
if (def.skipAutoTransform)
|
|
8626
|
+
return raw;
|
|
8627
|
+
return autoCoerce(raw, resolveDefault(def));
|
|
8628
|
+
}
|
|
8629
|
+
_writeToUrl(value) {
|
|
8630
|
+
const queryParams = {};
|
|
8631
|
+
for (const [key, def] of Object.entries(this._fields)) {
|
|
8632
|
+
const paramKey = this._paramKey(key);
|
|
8633
|
+
const fieldValue = value[key];
|
|
8634
|
+
const isDefault = equal(fieldValue, this._defaults[key]);
|
|
8635
|
+
const writeToUrl = def.appendToUrl !== false;
|
|
8636
|
+
const writeDefault = def.appendDefaultValueToUrl === true;
|
|
8637
|
+
if (!writeToUrl || (isDefault && !writeDefault)) {
|
|
8638
|
+
queryParams[paramKey] = undefined;
|
|
8639
|
+
}
|
|
8640
|
+
else if (def.valueToQueryParam) {
|
|
8641
|
+
queryParams[paramKey] = def.valueToQueryParam(fieldValue);
|
|
8642
|
+
}
|
|
8643
|
+
else {
|
|
8644
|
+
queryParams[paramKey] = fieldValue === null ? ET_NULL_VALUE : fieldValue;
|
|
8645
|
+
}
|
|
8646
|
+
}
|
|
8647
|
+
queueMicrotask(() => {
|
|
8648
|
+
this._router.navigate([], {
|
|
8649
|
+
queryParams,
|
|
8650
|
+
queryParamsHandling: 'merge',
|
|
8651
|
+
replaceUrl: this._observeOptions?.replaceUrl,
|
|
8652
|
+
});
|
|
8653
|
+
});
|
|
8654
|
+
}
|
|
8655
|
+
_paramKey(key) {
|
|
8656
|
+
const prefix = this._prefix;
|
|
8657
|
+
if (!prefix)
|
|
8658
|
+
return key;
|
|
8659
|
+
return `${typeof prefix === 'string' ? prefix : prefix()}-${key}`;
|
|
8660
|
+
}
|
|
8661
|
+
_clearTimer() {
|
|
8662
|
+
if (this._pendingTimer !== null) {
|
|
8663
|
+
clearTimeout(this._pendingTimer);
|
|
8664
|
+
this._pendingTimer = null;
|
|
8665
|
+
}
|
|
8666
|
+
}
|
|
8667
|
+
_cleanup() {
|
|
8668
|
+
if (!this._observing())
|
|
8669
|
+
return;
|
|
8670
|
+
this._observing.set(false);
|
|
8671
|
+
this._clearTimer();
|
|
8672
|
+
if (this._observeOptions?.writeToQueryParams === false)
|
|
8673
|
+
return;
|
|
8674
|
+
const queryParams = {};
|
|
8675
|
+
for (const key of Object.keys(this._fields)) {
|
|
8676
|
+
queryParams[this._paramKey(key)] = undefined;
|
|
8677
|
+
}
|
|
8678
|
+
queueMicrotask(() => {
|
|
8679
|
+
this._router.navigate([], { queryParams, queryParamsHandling: 'merge', replaceUrl: true });
|
|
8680
|
+
});
|
|
8681
|
+
}
|
|
8682
|
+
}
|
|
8683
|
+
/**
|
|
8684
|
+
* Create a {@link QueryFormSignals}. Call `.observe()` to start syncing with the
|
|
8685
|
+
* URL. Must be called in an injection context.
|
|
8686
|
+
*/
|
|
8687
|
+
const createQueryForm = (config) => new QueryFormSignals(config);
|
|
8688
|
+
|
|
8689
|
+
const normalizeConfig = (config) => {
|
|
8690
|
+
if (!config)
|
|
8691
|
+
return {};
|
|
8692
|
+
const { isResetBy, ...rest } = config;
|
|
8693
|
+
return {
|
|
8694
|
+
...rest,
|
|
8695
|
+
isResetBy: isResetBy === undefined ? undefined : Array.isArray(isResetBy) ? isResetBy : [isResetBy],
|
|
8696
|
+
};
|
|
8697
|
+
};
|
|
8698
|
+
/**
|
|
8699
|
+
* A generic query-form field. Defaults to `null` and, unless a transform is
|
|
8700
|
+
* given, relies on the auto-coercion when reading back from the URL.
|
|
8701
|
+
*
|
|
8702
|
+
* @example
|
|
8703
|
+
* queryField<number>({ defaultValue: 1, isResetBy: ['search'] })
|
|
8704
|
+
*/
|
|
8705
|
+
const queryField = (config) => ({
|
|
8706
|
+
defaultValue: null,
|
|
8707
|
+
...normalizeConfig(config),
|
|
8708
|
+
});
|
|
8709
|
+
/**
|
|
8710
|
+
* A debounced free-text search field. Debounces typing by 300ms but applies
|
|
8711
|
+
* clearing immediately (`disableDebounceIfFalsy`).
|
|
8712
|
+
*/
|
|
8713
|
+
const searchQueryField = (config) => ({
|
|
8714
|
+
defaultValue: null,
|
|
8715
|
+
debounce: 300,
|
|
8716
|
+
disableDebounceIfFalsy: true,
|
|
8717
|
+
...normalizeConfig(config),
|
|
8718
|
+
});
|
|
8719
|
+
/**
|
|
8720
|
+
* A sort field serialized as `"active:direction"` (e.g. `"name:asc"`). Matches
|
|
8721
|
+
* the wire format the table system's URL adapter uses, so the two interoperate.
|
|
8722
|
+
*/
|
|
8723
|
+
const sortQueryField = (config) => ({
|
|
8724
|
+
defaultValue: null,
|
|
8725
|
+
queryParamToValue: transformToSort,
|
|
8726
|
+
valueToQueryParam: transformToSortQueryParam,
|
|
8727
|
+
...normalizeConfig(config),
|
|
8728
|
+
});
|
|
8729
|
+
/** A field holding a list of strings. */
|
|
8730
|
+
const stringArrayQueryField = (config) => ({
|
|
8731
|
+
defaultValue: null,
|
|
8732
|
+
queryParamToValue: transformToStringArray,
|
|
8733
|
+
...normalizeConfig(config),
|
|
8734
|
+
});
|
|
8735
|
+
/** A field holding a list of numbers. */
|
|
8736
|
+
const numberArrayQueryField = (config) => ({
|
|
8737
|
+
defaultValue: null,
|
|
8738
|
+
queryParamToValue: transformToNumberArray,
|
|
8739
|
+
...normalizeConfig(config),
|
|
8740
|
+
});
|
|
8741
|
+
/** A field holding a list of booleans. */
|
|
8742
|
+
const booleanArrayQueryField = (config) => ({
|
|
8743
|
+
defaultValue: null,
|
|
8744
|
+
queryParamToValue: transformToBooleanArray,
|
|
8745
|
+
...normalizeConfig(config),
|
|
8746
|
+
});
|
|
8747
|
+
/** A single date field (expects an ISO/`Date`-parseable string in the URL). */
|
|
8748
|
+
const dateQueryField = (config) => ({
|
|
8749
|
+
defaultValue: null,
|
|
8750
|
+
queryParamToValue: transformToDate,
|
|
8751
|
+
...normalizeConfig(config),
|
|
8752
|
+
});
|
|
8753
|
+
/** A field holding a list of dates. */
|
|
8754
|
+
const dateArrayQueryField = (config) => ({
|
|
8755
|
+
defaultValue: null,
|
|
8756
|
+
queryParamToValue: transformToDateArray,
|
|
8757
|
+
...normalizeConfig(config),
|
|
8758
|
+
});
|
|
8759
|
+
|
|
7232
8760
|
// codes 1000-1999
|
|
7233
8761
|
const WebSocketRuntimeErrorCode = {
|
|
7234
8762
|
// Web Socket Client
|
|
@@ -7242,6 +8770,7 @@ const messageMalformed = () => {
|
|
|
7242
8770
|
return new RuntimeError(WebSocketRuntimeErrorCode.MESSAGE_MALFORMED, 'A message has been received but it is malformed (cannot be parsed as JSON).');
|
|
7243
8771
|
};
|
|
7244
8772
|
|
|
8773
|
+
const MAX_DEVTOOLS_MESSAGES = 100;
|
|
7245
8774
|
const createWebSocketClient = (options) => {
|
|
7246
8775
|
return createRootProvider(() => {
|
|
7247
8776
|
const socket = io(options.url, {
|
|
@@ -7250,21 +8779,36 @@ const createWebSocketClient = (options) => {
|
|
|
7250
8779
|
transports: options.transports,
|
|
7251
8780
|
});
|
|
7252
8781
|
const rooms = new Map();
|
|
7253
|
-
const isConnected = signal(false,
|
|
8782
|
+
const isConnected = signal(false, /* @ts-ignore */
|
|
8783
|
+
...(ngDevMode ? [{ debugName: "isConnected" }] : /* istanbul ignore next */ []));
|
|
8784
|
+
// Devtools instrumentation (no-op unless provideQueryDevtools() was called).
|
|
8785
|
+
const devtoolsEnabled = isQueryDevtoolsEnabled();
|
|
8786
|
+
const devtoolsRooms = signal([], /* @ts-ignore */
|
|
8787
|
+
...(ngDevMode ? [{ debugName: "devtoolsRooms" }] : /* istanbul ignore next */ []));
|
|
8788
|
+
const devtoolsMessages = signal([], /* @ts-ignore */
|
|
8789
|
+
...(ngDevMode ? [{ debugName: "devtoolsMessages" }] : /* istanbul ignore next */ []));
|
|
8790
|
+
let devtoolsMessageId = 0;
|
|
8791
|
+
const syncDevtoolsRooms = () => {
|
|
8792
|
+
if (devtoolsEnabled)
|
|
8793
|
+
devtoolsRooms.set([...rooms.keys()]);
|
|
8794
|
+
};
|
|
7254
8795
|
const joinRoom = (room) => {
|
|
7255
8796
|
const roomFn = typeof room === 'function' ? room : () => room;
|
|
7256
8797
|
const pre = previousSignalValue(computed(() => roomFn()));
|
|
7257
|
-
const roomData = signal(null,
|
|
8798
|
+
const roomData = signal(null, /* @ts-ignore */
|
|
8799
|
+
...(ngDevMode ? [{ debugName: "roomData" }] : /* istanbul ignore next */ []));
|
|
7258
8800
|
const join = (name) => {
|
|
7259
8801
|
socket.emit('join-room', name);
|
|
7260
8802
|
const existingRoom = rooms.get(name);
|
|
7261
8803
|
if (existingRoom)
|
|
7262
8804
|
return existingRoom;
|
|
7263
|
-
const message = signal(null,
|
|
8805
|
+
const message = signal(null, /* @ts-ignore */
|
|
8806
|
+
...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
7264
8807
|
const newRoom = {
|
|
7265
8808
|
latestMessage: message,
|
|
7266
8809
|
};
|
|
7267
8810
|
rooms.set(name, newRoom);
|
|
8811
|
+
syncDevtoolsRooms();
|
|
7268
8812
|
return newRoom;
|
|
7269
8813
|
};
|
|
7270
8814
|
effect(() => {
|
|
@@ -7301,6 +8845,7 @@ const createWebSocketClient = (options) => {
|
|
|
7301
8845
|
}
|
|
7302
8846
|
socket.emit('leave-room', room);
|
|
7303
8847
|
rooms.delete(room);
|
|
8848
|
+
syncDevtoolsRooms();
|
|
7304
8849
|
};
|
|
7305
8850
|
const setupWebSocketConnectionListener = () => {
|
|
7306
8851
|
socket.on('connect', () => {
|
|
@@ -7315,6 +8860,16 @@ const createWebSocketClient = (options) => {
|
|
|
7315
8860
|
socket.onAny((data) => {
|
|
7316
8861
|
try {
|
|
7317
8862
|
const json = JSON.parse(data);
|
|
8863
|
+
if (devtoolsEnabled) {
|
|
8864
|
+
const message = {
|
|
8865
|
+
id: devtoolsMessageId++,
|
|
8866
|
+
timestamp: Date.now(),
|
|
8867
|
+
room: json.room,
|
|
8868
|
+
event: json.event,
|
|
8869
|
+
data: json.data,
|
|
8870
|
+
};
|
|
8871
|
+
devtoolsMessages.update((log) => [message, ...log].slice(0, MAX_DEVTOOLS_MESSAGES));
|
|
8872
|
+
}
|
|
7318
8873
|
const room = rooms.get(json.room);
|
|
7319
8874
|
if (room)
|
|
7320
8875
|
room.latestMessage.set(json);
|
|
@@ -7337,6 +8892,19 @@ const createWebSocketClient = (options) => {
|
|
|
7337
8892
|
leaveRoom,
|
|
7338
8893
|
},
|
|
7339
8894
|
};
|
|
8895
|
+
if (devtoolsEnabled) {
|
|
8896
|
+
const handle = {
|
|
8897
|
+
connected: isConnected.asReadonly(),
|
|
8898
|
+
rooms: devtoolsRooms.asReadonly(),
|
|
8899
|
+
messages: devtoolsMessages.asReadonly(),
|
|
8900
|
+
};
|
|
8901
|
+
const unregister = registerQueryDevtoolsEntry({
|
|
8902
|
+
kind: 'ws-client',
|
|
8903
|
+
handle,
|
|
8904
|
+
meta: { name: options.name, url: options.url },
|
|
8905
|
+
});
|
|
8906
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
8907
|
+
}
|
|
7340
8908
|
return client;
|
|
7341
8909
|
}, {
|
|
7342
8910
|
name: `WebSocketClient_${options.name}`,
|
|
@@ -7347,5 +8915,5 @@ const createWebSocketClient = (options) => {
|
|
|
7347
8915
|
* Generated bundle index. Do not edit.
|
|
7348
8916
|
*/
|
|
7349
8917
|
|
|
7350
|
-
export { BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CLEAR_QUERY_ARGS, CustomHeaderAuthProvider, DateArrayQueryField, DateQueryField, EntityStore, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, LegacyQuery, LegacyQueryCreator, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QueryDevtoolsComponent, QueryDirective, QueryError, QueryFeatureType, QueryField, QueryForm, QueryRuntimeErrorCode, QueryShortNamePipe, QueryStore, SPEED_BUFFER_TIME_IN_MS, SearchQueryField, SortQueryField, StringArrayQueryField, V2BearerAuthProvider, V2Query, V2QueryClient, V2QueryCreator, addQueryContainerHandling, applyQueryFeatures, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, buildErrorMessage, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, contentfulGqlLikePaginationAdapter, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHttpRequest, createInfinityQueryConfig, createLegacyQueryCreator, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryErrorResponse, createQueryFeature, createQueryObject, createQueryRepository, createQuerySnapshotFn, createQueryStack, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, decryptBearer, deepFreeze, def, detectContentTypeHeader, dynLikePaginationAdapter, effectComputed, ethletePaginationAdapter, extractExpiresInSeconds, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, getDefaultHeaders, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, hasHeader, ignoreAutoRefresh, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isEmptyString, isGqlQueryConfig, isLegacyQuery, isNaN$1 as isNaN, isQuery, isQueryCollection, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, mapToPaginated, maybeExecute, mergeHeaders, nestedEffect, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideQueryClientForDevtools, queryArrayComputed, queryComputed, queryComputedTillTruthy, queryExecute, queryFeatureUsedMultipleTimes, queryStackTotalQueriesAndExpectedQueriesMismatch, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, removeFrom, request, resetExecuteState, resetPageOnError, serializeBody, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, splitQueryConfig, switchQueryCollectionState, switchQueryState, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, transformArrayResponse, transformExecStateToQueryState, transformGql, transformMethod, transformPaginatedResponse, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray, uncacheableRequestHasAllowCacheParam, uncacheableRequestHasCacheKeyParam, v2BuildQueryCacheKey, v2ExtractExpiresInSeconds, v2ShouldCacheQuery, v2ShouldRetryRequest, withArgs, withArgsQueryFeatureMissingButRouteIsFunction, withAuthenticationQuery, withAutoRefresh, withAutoRefreshUsedInManualQuery, withAutoRefreshUsedOnUnsupportedHttpMethod, withErrorHandling, withInactivityLogout, withLogging, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withRefreshQuery, withResponseUpdate, withSuccessHandling, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal };
|
|
8918
|
+
export { BasicAuthProvider, BearerAuthFeatureType, BooleanArrayQueryField, CLEAR_QUERY_ARGS, CustomHeaderAuthProvider, DEFAULT_KEEP_UNUSED_FOR, DateArrayQueryField, DateQueryField, EntityStore, IGNORED_FILTER_COUNT_FIELDS, INFINITY_QUERY_TOKEN, InfinityQuery, InfinityQueryDirective, InfinityQueryTriggerDirective, LegacyQuery, LegacyQueryCreator, MAX_UNUSED_ENTRIES, NumberArrayQueryField, ParseHttpErrorCodeToMessageDePipe, ParseHttpErrorCodeToMessageEnPipe, ParseHttpErrorCodeToTitleDePipe, ParseHttpErrorCodeToTitleEnPipe, QUERY_CLIENT_DEVTOOLS_TOKEN, QueryDevtoolsComponent, QueryDirective, QueryError, QueryFeatureType, QueryField, QueryForm, QueryFormSignals, QueryRuntimeErrorCode, QueryShortNamePipe, QueryStore, SERVER_ERROR_KIND, SERVER_VIOLATION_ERROR_KIND, SPEED_BUFFER_TIME_IN_MS, SearchQueryField, SortQueryField, StringArrayQueryField, V2BearerAuthProvider, V2Query, V2QueryClient, V2QueryCreator, addQueryContainerHandling, applyQueryFeatures, authExtractTokensResponseMissingAccessToken, authExtractTokensResponseMissingRefreshToken, authExtractTokensResponseNotObject, authProviderFeatureUsedMultipleTimes, booleanArrayQueryField, buildErrorMessage, buildQueryCacheKey, buildQueryString, buildRoute, buildTimestampFromSeconds, castQueryCreatorTypes, circularQueryDependency, circularQueryDependencyChecker, computeQueryAuthHeader, computeQueryBody, computeQueryHeaders, computeQueryMethod, computeQueryQueryParams, consumeSuppressQueryStackDevtools, contentfulGqlLikePaginationAdapter, createBaseQuery, createBaseQueryCreator, createBearerAuthProvider, createDeleteQuery, createExecuteFn, createGetQuery, createGqlExecuteFn, createGqlMutationViaGet, createGqlMutationViaPost, createGqlQuery, createGqlQueryCreator, createGqlQueryViaGet, createGqlQueryViaPost, createHttpRequest, createInfinityQueryConfig, createLegacyQueryCreator, createPagedQueryStack, createPatchQuery, createPersistentAuthFeature, createPostQuery, createPutQuery, createQuery, createQueryClient, createQueryCollection, createQueryCollectionSignal, createQueryCollectionSubject, createQueryContext, createQueryCreator, createQueryErrorResponse, createQueryFeature, createQueryForm, createQueryObject, createQueryRepository, createQuerySnapshotFn, createQueryStack, createSecureDeleteQuery, createSecureExecuteFactory, createSecureExecuteFn, createSecureGetQuery, createSecureGqlExecuteFn, createSecureGqlMutationViaGet, createSecureGqlMutationViaPost, createSecureGqlQuery, createSecureGqlQueryCreator, createSecureGqlQueryViaGet, createSecureGqlQueryViaPost, createSecurePatchQuery, createSecurePostQuery, createSecurePutQuery, createSecureQuery, createSecureQueryCreator, createTrackingFeature, createWebSocketClient, dateArrayQueryField, dateQueryField, decryptBearer, deepFreeze, def, detectContentTypeHeader, dynLikePaginationAdapter, effectComputed, emptyQueryArgs, ethletePaginationAdapter, executeUntilSettled, extractExpiresInSeconds, extractFormViolations, extractQuery, fakePaginationAdapter, filterFailure, filterNull, filterQueryStates, filterSuccess, forEachHeader, getDefaultHeaders, getQueryClientName, getQueryFeatureUsage, getResponseUrl, ggLikePaginationAdapter, gql, gqlDataPropertyMissingInResponse, hasHeader, ignoreAutoRefresh, injectInfinityQueryResponseDelay, injectQueryContext, insertFrom, invalidBaseRouteError, invalidBodyError, invalidRouteError, invalidStateInsideSecureExecuteFactory, isAutoRefresh, isBasicAuthProvider, isBearerAuthProvider, isClassValidatorError, isCreateGqlQueryOptions, isCustomHeaderAuthProvider, isEmptyString, isGqlQueryConfig, isLegacyQuery, isNaN$1 as isNaN, isPageOutOfRangeError, isQuery, isQueryCollection, isQueryDevtoolsEnabled, isQueryErrorResponse, isQueryStateCancelled, isQueryStateFailure, isQueryStateLoading, isQueryStatePrepared, isQueryStateSuccess, isRequestError, isSymfonyError, isSymfonyFormViolationListError, isSymfonyListError, isSymfonyPagerfantaOutOfRangeError, legacyPrepareWithoutInjectionContext, mapToPaginated, mapViolationsToFormErrors, maybeExecute, mergeHeaders, nestedEffect, numberArrayQueryField, pagedQueryStackNextPageCalledWithoutPreviousPage, pagedQueryStackPageBiggerThanTotalPages, pagedQueryStackPreviousPageCalledButAlreadyAtFirstPage, paginatedEntityValueUpdater, parseAllXhrResponseHeaders, parseHttpErrorCodeToMessageDe, parseHttpErrorCodeToMessageEn, parseHttpErrorCodeToTitleDe, parseHttpErrorCodeToTitleEn, pathParamsMissingInRouteFunctionError, provideInfinityQueryResponseDelay, provideQueryClientForDevtools, provideQueryDevtools, queryArrayComputed, queryComputed, queryComputedTillTruthy, queryDevtoolsEntries, queryErrorMessage, queryErrorMessages, queryExecute, queryFeatureUsedMultipleTimes, queryField, querySequence, querySequenceAlreadyRunning, queryStackTotalQueriesAndExpectedQueriesMismatch, queryStackWithArgsUsed, queryStackWithResponseUpdateUsed, queryStateAlreadyHasKeyError, queryStateCannotTransformError, queryStateDoesNotContainKeyError, queryStateErrorSignal, queryStateLoadingSignal, queryStateResponseSignal, queryStateSignal, registerQueryDevtoolsEntry, removeFrom, request, resetExecuteState, resetPageOnError, searchQueryField, serializeBody, setupQueryDependencies, setupQueryExecuteState, setupQueryState, shouldAutoExecuteGqlQuery, shouldAutoExecuteQuery, shouldCacheQuery, shouldRetryRequest, silenceMissingWithArgsFeatureErrorUsedButWithArgsPresent, skipPaginationPageParamCalculator, sortQueryField, splitQueryConfig, stringArrayQueryField, stringifyQueryRoute, suppressNextQueryStackDevtools, switchQueryCollectionState, switchQueryState, takeUntilResponse, toQuerySignal, toQuerySubject, tokensNotAvailableInsideAuthAndExec, transformArrayResponse, transformExecStateToQueryState, transformGql, transformMethod, transformPaginatedResponse, transformToBoolean, transformToBooleanArray, transformToDate, transformToDateArray, transformToNumber, transformToNumberArray, transformToSort, transformToSortQueryParam, transformToString, transformToStringArray, uncacheableRequestHasAllowCacheParam, uncacheableRequestHasCacheKeyParam, v2BuildQueryCacheKey, v2ExtractExpiresInSeconds, v2ShouldCacheQuery, v2ShouldRetryRequest, validateWithQuery, validateWithV2Query, withArgs, withArgsQueryFeatureMissingButRouteIsFunction, withAuthenticationQuery, withAutoRefresh, withAutoRefreshUsedInManualQuery, withAutoRefreshUsedOnUnsupportedHttpMethod, withErrorHandling, withInactivityLogout, withLogging, withPageResetOnError, withPersistentAuth, withPolling, withPollingUsedOnUnsupportedHttpMethod, withRefreshQuery, withResponseUpdate, withSuccessHandling, withTokenExpirationWarning, withTokenRevocation, withTracking, wrapAsObservableSignal };
|
|
7351
8919
|
//# sourceMappingURL=ethlete-query.mjs.map
|