ngx-signal-query 1.5.0-dev.1 → 1.5.0-dev.3
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/fesm2022/ngx-signal-query.mjs +270 -106
- package/fesm2022/ngx-signal-query.mjs.map +1 -1
- package/index.d.ts +114 -19
- package/package.json +1 -1
|
@@ -232,6 +232,39 @@ function isValidTimeout(value) {
|
|
|
232
232
|
function isPromiseLike(value) {
|
|
233
233
|
return typeof value?.then === 'function';
|
|
234
234
|
}
|
|
235
|
+
// Resolves once every promise has settled, and never rejects. It resolves
|
|
236
|
+
// with nothing: what the promises brought isn't the caller's to see.
|
|
237
|
+
/** @internal */
|
|
238
|
+
function whenAllSettled(promises) {
|
|
239
|
+
return Promise.allSettled(promises).then(() => undefined);
|
|
240
|
+
}
|
|
241
|
+
/** @internal */
|
|
242
|
+
function deferred() {
|
|
243
|
+
let resolve;
|
|
244
|
+
let reject;
|
|
245
|
+
const promise = new Promise((res, rej) => {
|
|
246
|
+
resolve = res;
|
|
247
|
+
reject = rej;
|
|
248
|
+
});
|
|
249
|
+
return { promise, resolve, reject };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* The error a promise of a query's data rejects with when the fetch stops
|
|
254
|
+
* with no data to give: it was cancelled before the query had any
|
|
255
|
+
* (`cancelQueries()`, or the last component showing the query went away), or
|
|
256
|
+
* the query was removed (`removeQueries()`, e.g. on sign-out). The request
|
|
257
|
+
* didn't fail, so tell the two apart with `instanceof CancelledError`.
|
|
258
|
+
*
|
|
259
|
+
* A mutation's `mutateAsync()` rejects with one too, when the mutation cache
|
|
260
|
+
* is cleared while its run is in flight.
|
|
261
|
+
*/
|
|
262
|
+
class CancelledError extends Error {
|
|
263
|
+
constructor(message = 'The query fetch was cancelled') {
|
|
264
|
+
super(message);
|
|
265
|
+
this.name = 'CancelledError';
|
|
266
|
+
}
|
|
267
|
+
}
|
|
235
268
|
|
|
236
269
|
// Exponential backoff capped at 30s, matching TanStack's default.
|
|
237
270
|
/** @internal */
|
|
@@ -273,6 +306,9 @@ class Query {
|
|
|
273
306
|
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
274
307
|
state = this.#state.asReadonly();
|
|
275
308
|
#subscription = null;
|
|
309
|
+
// The promise of the fetch in flight, which every caller that joins the
|
|
310
|
+
// fetch shares, and how to settle it; null while nothing is in flight.
|
|
311
|
+
#outcome = null;
|
|
276
312
|
// The structuralSharing of the fetch whose response the query holds.
|
|
277
313
|
#fetchedStructuralSharing;
|
|
278
314
|
#observers = new Set();
|
|
@@ -306,15 +342,17 @@ class Query {
|
|
|
306
342
|
this.#scheduleGc();
|
|
307
343
|
}
|
|
308
344
|
}
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
|
|
345
|
+
// Refetches the query if a component shows it: the first observer that
|
|
346
|
+
// wants it fetched decides how, as in TanStack. A fetch in flight is
|
|
347
|
+
// replaced, as it may predate the change the caller is after. Returns the
|
|
348
|
+
// fetch's promise, or undefined if no observer wants the query fetched.
|
|
349
|
+
refetchIfActive() {
|
|
312
350
|
for (const observer of this.#observers) {
|
|
313
351
|
const options = observer.fetchOptions();
|
|
314
352
|
if (options)
|
|
315
|
-
return options;
|
|
353
|
+
return this.fetch(options, { cancelRefetch: true });
|
|
316
354
|
}
|
|
317
|
-
return
|
|
355
|
+
return;
|
|
318
356
|
}
|
|
319
357
|
// Refetches the query now that the user is back on the page, if one of its
|
|
320
358
|
// observers wants that: its refetchOnWindowFocus is 'always', or true while
|
|
@@ -338,14 +376,28 @@ class Query {
|
|
|
338
376
|
}
|
|
339
377
|
// Without a structuralSharing setting the response is stored as is: the
|
|
340
378
|
// default is the client's to resolve.
|
|
379
|
+
//
|
|
380
|
+
// Returns a promise of the data as the query stores it, which every caller
|
|
381
|
+
// that joins the fetch shares. A fetch that replaces this one settles it
|
|
382
|
+
// with its own outcome; one stopped with nothing in its place settles it as
|
|
383
|
+
// cancel() or destroy() says. Nobody has to wait for it.
|
|
341
384
|
fetch({ queryFn, retry: retry$1 = 0, retryDelay = defaultRetryDelay, structuralSharing = false, }, { cancelRefetch = false } = {}) {
|
|
342
|
-
|
|
385
|
+
const running = this.#outcome;
|
|
386
|
+
if (running) {
|
|
343
387
|
// Already in-flight: dedupe unless the caller explicitly wants a fresh
|
|
344
|
-
// fetch (e.g. invalidate/refetch) — then
|
|
388
|
+
// fetch (e.g. invalidate/refetch) — then replace the old one.
|
|
345
389
|
if (!cancelRefetch)
|
|
346
|
-
return;
|
|
347
|
-
this
|
|
390
|
+
return running.promise;
|
|
391
|
+
this.#subscription?.unsubscribe();
|
|
348
392
|
}
|
|
393
|
+
const outcome = deferred();
|
|
394
|
+
// Its result or error lands in the state either way, so a caller may
|
|
395
|
+
// ignore the promise: handled here, a failure doesn't surface as an
|
|
396
|
+
// unhandled rejection, and whoever awaits the promise still gets it.
|
|
397
|
+
outcome.promise.catch(() => undefined);
|
|
398
|
+
// Whoever waits for the fetch this one replaces gets this one's outcome.
|
|
399
|
+
running?.resolve(outcome.promise);
|
|
400
|
+
this.#outcome = outcome;
|
|
349
401
|
this.#state.update((state) => ({
|
|
350
402
|
...state,
|
|
351
403
|
isFetching: true,
|
|
@@ -377,6 +429,7 @@ class Query {
|
|
|
377
429
|
map((data) => replaceData(this.state().data, data, structuralSharing)))
|
|
378
430
|
.subscribe({
|
|
379
431
|
next: (data) => {
|
|
432
|
+
this.#outcome = null;
|
|
380
433
|
this.#fetchedStructuralSharing = structuralSharing;
|
|
381
434
|
this.#state.set({
|
|
382
435
|
data,
|
|
@@ -388,14 +441,20 @@ class Query {
|
|
|
388
441
|
failureReason: null,
|
|
389
442
|
updatedAt: Date.now(),
|
|
390
443
|
});
|
|
444
|
+
outcome.resolve(data);
|
|
445
|
+
},
|
|
446
|
+
error: (err) => {
|
|
447
|
+
this.#outcome = null;
|
|
448
|
+
this.#state.update((state) => ({
|
|
449
|
+
...state,
|
|
450
|
+
status: 'error',
|
|
451
|
+
error: err,
|
|
452
|
+
isFetching: false,
|
|
453
|
+
}));
|
|
454
|
+
outcome.reject(err);
|
|
391
455
|
},
|
|
392
|
-
error: (err) => this.#state.update((state) => ({
|
|
393
|
-
...state,
|
|
394
|
-
status: 'error',
|
|
395
|
-
error: err,
|
|
396
|
-
isFetching: false,
|
|
397
|
-
})),
|
|
398
456
|
});
|
|
457
|
+
return outcome.promise;
|
|
399
458
|
}
|
|
400
459
|
// Without a structuralSharing setting the data is stored as is: which one
|
|
401
460
|
// a write uses is the client's to resolve.
|
|
@@ -424,17 +483,18 @@ class Query {
|
|
|
424
483
|
state.isInvalidated ||
|
|
425
484
|
this.#isStale(staleTime));
|
|
426
485
|
}
|
|
486
|
+
// Stops the fetch in flight. Whoever waits for it gets the data the query
|
|
487
|
+
// holds, as in TanStack, or a CancelledError if it holds none.
|
|
427
488
|
cancel() {
|
|
428
|
-
this
|
|
429
|
-
this.#
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
if (this.state().isFetching) {
|
|
433
|
-
this.#state.update((state) => ({ ...state, isFetching: false }));
|
|
434
|
-
}
|
|
489
|
+
const { data } = this.state();
|
|
490
|
+
this.#stop((outcome) => data === undefined
|
|
491
|
+
? outcome.reject(new CancelledError())
|
|
492
|
+
: outcome.resolve(data));
|
|
435
493
|
}
|
|
494
|
+
// The query is thrown away with its data, so whoever waits for its fetch
|
|
495
|
+
// gets a CancelledError, as in TanStack.
|
|
436
496
|
destroy() {
|
|
437
|
-
this.
|
|
497
|
+
this.#stop((outcome) => outcome.reject(new CancelledError()));
|
|
438
498
|
this.#clearGcTimer();
|
|
439
499
|
}
|
|
440
500
|
// Inclusive: data fetched exactly `staleTime` ago is already stale. This
|
|
@@ -445,7 +505,21 @@ class Query {
|
|
|
445
505
|
return Date.now() - this.state().updatedAt >= staleTime;
|
|
446
506
|
}
|
|
447
507
|
#inFlight() {
|
|
448
|
-
return this.#
|
|
508
|
+
return this.#outcome !== null;
|
|
509
|
+
}
|
|
510
|
+
// Stops the fetch in flight, if any, and settles its promise with settle.
|
|
511
|
+
#stop(settle) {
|
|
512
|
+
const outcome = this.#outcome;
|
|
513
|
+
this.#subscription?.unsubscribe();
|
|
514
|
+
this.#subscription = null;
|
|
515
|
+
this.#outcome = null;
|
|
516
|
+
if (outcome)
|
|
517
|
+
settle(outcome);
|
|
518
|
+
// The fetch is no longer running; clear the in-flight flag so isFetching
|
|
519
|
+
// doesn't stay stuck true after a cancellation.
|
|
520
|
+
if (this.state().isFetching) {
|
|
521
|
+
this.#state.update((state) => ({ ...state, isFetching: false }));
|
|
522
|
+
}
|
|
449
523
|
}
|
|
450
524
|
// A fetch in flight is kept, as in TanStack, and the observers aren't even
|
|
451
525
|
// asked: fetch() would drop the refetch anyway.
|
|
@@ -551,6 +625,9 @@ class Mutation {
|
|
|
551
625
|
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
552
626
|
state = this.#state.asReadonly();
|
|
553
627
|
#subscription = null;
|
|
628
|
+
// The promise of the run in flight and how to settle it; null once it has
|
|
629
|
+
// settled, or before the run.
|
|
630
|
+
#run = null;
|
|
554
631
|
#observers = 0;
|
|
555
632
|
#options;
|
|
556
633
|
#cache;
|
|
@@ -577,8 +654,14 @@ class Mutation {
|
|
|
577
654
|
this.#observers--;
|
|
578
655
|
this.#disposeIfDone();
|
|
579
656
|
}
|
|
580
|
-
/**
|
|
657
|
+
/**
|
|
658
|
+
* Runs the mutation once. `variables` are passed straight to `mutationFn`.
|
|
659
|
+
* Returns a promise of the run, settled as the state is, once its last hook
|
|
660
|
+
* is done: with the data, or with the error.
|
|
661
|
+
*/
|
|
581
662
|
execute(variables) {
|
|
663
|
+
const run = deferred();
|
|
664
|
+
this.#run = run;
|
|
582
665
|
this.#state.set({
|
|
583
666
|
status: 'pending',
|
|
584
667
|
data: undefined,
|
|
@@ -607,12 +690,24 @@ class Mutation {
|
|
|
607
690
|
})), concatMap((outcome) => this.#runHooks(outcome, variables).pipe(endWith(outcome))))
|
|
608
691
|
.subscribe((outcome) => {
|
|
609
692
|
this.#state.update((state) => ({ ...state, ...outcome }));
|
|
693
|
+
this.#run = null;
|
|
610
694
|
this.#disposeIfDone();
|
|
695
|
+
if (outcome.status === 'success') {
|
|
696
|
+
run.resolve(outcome.data);
|
|
697
|
+
}
|
|
698
|
+
else {
|
|
699
|
+
run.reject(outcome.error);
|
|
700
|
+
}
|
|
611
701
|
});
|
|
702
|
+
return run.promise;
|
|
612
703
|
}
|
|
704
|
+
// Whoever waits for the run hears that it was cancelled, so its promise
|
|
705
|
+
// doesn't hang.
|
|
613
706
|
cancel() {
|
|
614
707
|
this.#subscription?.unsubscribe();
|
|
615
708
|
this.#subscription = null;
|
|
709
|
+
this.#run?.reject(new CancelledError('The mutation was cancelled'));
|
|
710
|
+
this.#run = null;
|
|
616
711
|
}
|
|
617
712
|
/** Sends the write, retrying as `retry` allows, and emits its data once. */
|
|
618
713
|
#request(variables) {
|
|
@@ -804,24 +899,58 @@ class QueryClient {
|
|
|
804
899
|
/**
|
|
805
900
|
* Imperatively fetches and caches a query, unless fresh data already exists
|
|
806
901
|
* (governed by `staleTime`). Prefer {@link injectQuery} in components; use
|
|
807
|
-
* this to prefetch, from an event handler or an effect
|
|
902
|
+
* this to prefetch, from an event handler or an effect, or to wait for the
|
|
903
|
+
* data, e.g. in a route resolver.
|
|
904
|
+
*
|
|
905
|
+
* Resolves with the data as the cache holds it: at once if it's fresh,
|
|
906
|
+
* otherwise once the fetch lands. A key already fetching is waited for,
|
|
907
|
+
* not fetched again, unless `cancelRefetch` is set. Rejects with the error
|
|
908
|
+
* if the fetch fails. A cancelled fetch resolves with the data the cache
|
|
909
|
+
* holds, or rejects with a {@link CancelledError} if it holds none; a
|
|
910
|
+
* removed query always rejects with one. Nothing has to wait for the
|
|
911
|
+
* promise: a prefetch that fails only leaves the query in `'error'`.
|
|
912
|
+
*
|
|
913
|
+
* Unlike a component's query, it doesn't retry a failure unless `retry` is
|
|
914
|
+
* set here or in {@link withDefaultOptions}, so whoever waits for the data
|
|
915
|
+
* hears of the failure at once.
|
|
808
916
|
*
|
|
809
917
|
* @param key - The query key to fetch and cache under.
|
|
810
918
|
* @param queryFn - Function returning the data as an `Observable` or `Promise`.
|
|
811
919
|
* @param options - Fetch tuning (`staleTime`, `retry`, `retryDelay`,
|
|
812
920
|
* `structuralSharing`, `cancelRefetch`).
|
|
921
|
+
* @returns A promise of the data.
|
|
922
|
+
*
|
|
923
|
+
* @example
|
|
924
|
+
* ```ts
|
|
925
|
+
* export const todoResolver: ResolveFn<Todo> = (route) => {
|
|
926
|
+
* const http = inject(HttpClient)
|
|
927
|
+
* const id = Number(route.paramMap.get('id'))
|
|
928
|
+
*
|
|
929
|
+
* return injectQueryClient().fetchQuery(['todos', id], () =>
|
|
930
|
+
* http.get<Todo>(`/api/todos/${id}`),
|
|
931
|
+
* )
|
|
932
|
+
* }
|
|
933
|
+
* ```
|
|
813
934
|
*/
|
|
814
935
|
fetchQuery(key, queryFn, options = {}) {
|
|
815
|
-
untracked(() => {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
const
|
|
936
|
+
return untracked(() => {
|
|
937
|
+
// Resolved as a query's options are, so one set of defaults serves
|
|
938
|
+
// both, except that a failure isn't retried unless asked: whoever waits
|
|
939
|
+
// for the data hears of it at once. cancelRefetch is about this call,
|
|
940
|
+
// not an option of the query.
|
|
941
|
+
const { cancelRefetch, ...queryOptions } = options;
|
|
942
|
+
const defaulted = this.defaultQueryOptions({
|
|
943
|
+
...queryOptions,
|
|
944
|
+
queryKey: key,
|
|
945
|
+
queryFn,
|
|
946
|
+
retry: queryOptions.retry ??
|
|
947
|
+
this.#config.defaultOptions?.queries?.retry ??
|
|
948
|
+
false,
|
|
949
|
+
});
|
|
821
950
|
const query = this.#cache.getOrCreate(key);
|
|
822
|
-
|
|
823
|
-
query.fetch(
|
|
824
|
-
|
|
951
|
+
return query.shouldFetch(defaulted.staleTime)
|
|
952
|
+
? query.fetch(defaulted, { cancelRefetch })
|
|
953
|
+
: Promise.resolve(query.state().data);
|
|
825
954
|
});
|
|
826
955
|
}
|
|
827
956
|
/**
|
|
@@ -872,11 +1001,16 @@ class QueryClient {
|
|
|
872
1001
|
* Marks matching queries as stale and refetches the actively observed ones,
|
|
873
1002
|
* once per query however many components observe it. The refetch starts
|
|
874
1003
|
* right away and replaces a fetch already in flight, which may predate the
|
|
875
|
-
* change
|
|
876
|
-
*
|
|
877
|
-
*
|
|
1004
|
+
* change. A query with no enabled observer keeps the stale mark and
|
|
1005
|
+
* refetches as soon as one appears. Commonly called after a mutation
|
|
1006
|
+
* succeeds.
|
|
1007
|
+
*
|
|
1008
|
+
* Resolves once those refetches have settled, so a mutation hook that
|
|
1009
|
+
* returns it keeps the mutation pending until the data is fresh. It never
|
|
1010
|
+
* rejects: a refetch that fails shows in its query's `error`.
|
|
878
1011
|
*
|
|
879
1012
|
* @param filters - Which queries to invalidate; omit to invalidate all.
|
|
1013
|
+
* `refetchType: 'none'` only marks them, and resolves at once.
|
|
880
1014
|
*
|
|
881
1015
|
* @example
|
|
882
1016
|
* ```ts
|
|
@@ -884,21 +1018,36 @@ class QueryClient {
|
|
|
884
1018
|
* ```
|
|
885
1019
|
*/
|
|
886
1020
|
invalidateQueries(filters) {
|
|
887
|
-
untracked(() => {
|
|
1021
|
+
return untracked(() => {
|
|
888
1022
|
const queries = this.#cache.findAll(filters);
|
|
889
1023
|
queries.forEach((query) => query.invalidate());
|
|
890
|
-
//
|
|
891
|
-
//
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
const options = query.activeFetchOptions();
|
|
896
|
-
if (options) {
|
|
897
|
-
query.fetch(options, { cancelRefetch: true });
|
|
898
|
-
}
|
|
899
|
-
});
|
|
1024
|
+
// An inactive query refetches when next observed, as shouldFetch sees
|
|
1025
|
+
// the flag.
|
|
1026
|
+
return filters?.refetchType === 'none'
|
|
1027
|
+
? Promise.resolve()
|
|
1028
|
+
: whenAllSettled(queries.map((query) => query.refetchIfActive()));
|
|
900
1029
|
});
|
|
901
1030
|
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Refetches the matching queries that a component shows, whether their
|
|
1033
|
+
* data is stale or not, once per query however many components show it. A
|
|
1034
|
+
* fetch already in flight is replaced. Queries nobody shows are left as
|
|
1035
|
+
* they are, not even marked stale: to have them refetch once they're shown,
|
|
1036
|
+
* invalidate them with `invalidateQueries()`.
|
|
1037
|
+
*
|
|
1038
|
+
* Resolves once the refetches have settled. It never rejects: a refetch
|
|
1039
|
+
* that fails shows in its query's `error`.
|
|
1040
|
+
*
|
|
1041
|
+
* @param filters - Which queries to refetch; omit to refetch all.
|
|
1042
|
+
*
|
|
1043
|
+
* @example
|
|
1044
|
+
* ```ts
|
|
1045
|
+
* await client.refetchQueries({ queryKey: ['todos'] })
|
|
1046
|
+
* ```
|
|
1047
|
+
*/
|
|
1048
|
+
refetchQueries(filters) {
|
|
1049
|
+
return untracked(() => whenAllSettled(this.#cache.findAll(filters).map((query) => query.refetchIfActive())));
|
|
1050
|
+
}
|
|
902
1051
|
/**
|
|
903
1052
|
* Cancels any in-flight fetches for matching queries.
|
|
904
1053
|
*
|
|
@@ -1235,18 +1384,19 @@ function createQueryObserver(optionsFn, injector) {
|
|
|
1235
1384
|
// Invalidation isn't a trigger: invalidateQueries() refetches each active
|
|
1236
1385
|
// query once itself. A query it skipped (unobserved, or its observers
|
|
1237
1386
|
// disabled) stays invalidated, so it fetches here once observed or enabled:
|
|
1238
|
-
// shouldFetch sees the flag.
|
|
1387
|
+
// shouldFetch sees the flag. The key and queryFn come from one read of the
|
|
1388
|
+
// options, so the fetch can't go to another key's query, whatever order the
|
|
1389
|
+
// effects run in.
|
|
1239
1390
|
track(() => {
|
|
1240
1391
|
query();
|
|
1241
1392
|
if (!enabled())
|
|
1242
1393
|
return;
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
}));
|
|
1394
|
+
untracked(() => {
|
|
1395
|
+
const options = defaultedOptions();
|
|
1396
|
+
const q = resolve(options.queryKey);
|
|
1397
|
+
if (q.shouldFetch(options.staleTime))
|
|
1398
|
+
q.fetch(options);
|
|
1399
|
+
});
|
|
1250
1400
|
});
|
|
1251
1401
|
// The polling interval this observer asks for, or false for none. Memoized,
|
|
1252
1402
|
// so the polling effect wakes only when the value changes. The function form
|
|
@@ -1314,34 +1464,37 @@ function createQueryObserver(optionsFn, injector) {
|
|
|
1314
1464
|
}
|
|
1315
1465
|
return { data: state.data, status: state.status, isPlaceholderData: false };
|
|
1316
1466
|
}, ...(ngDevMode ? [{ debugName: "resolved" }] : []));
|
|
1467
|
+
const result = {
|
|
1468
|
+
data: computed(() => resolved().data),
|
|
1469
|
+
status: computed(() => resolved().status),
|
|
1470
|
+
error: computed(() => query().state().error),
|
|
1471
|
+
isFetching: computed(() => query().state().isFetching),
|
|
1472
|
+
// First fetch in-flight: fetching with no resolved data yet. An active
|
|
1473
|
+
// placeholder counts as data — the UI shows it, not a loading state.
|
|
1474
|
+
isLoading: computed(() => query().state().isFetching && resolved().status === 'pending'),
|
|
1475
|
+
isPending: computed(() => resolved().status === 'pending'),
|
|
1476
|
+
isSuccess: computed(() => resolved().status === 'success'),
|
|
1477
|
+
isError: computed(() => resolved().status === 'error'),
|
|
1478
|
+
isPlaceholderData: computed(() => resolved().isPlaceholderData),
|
|
1479
|
+
failureCount: computed(() => query().state().failureCount),
|
|
1480
|
+
failureReason: computed(() => query().state().failureReason),
|
|
1481
|
+
// Force a fresh fetch regardless of staleTime and enabled, cancelling
|
|
1482
|
+
// any in-flight request (explicit user intent — get current data).
|
|
1483
|
+
// Untracked, as the client's methods are: called from an effect, it
|
|
1484
|
+
// mustn't make the effect depend on the options or on the query. However
|
|
1485
|
+
// the fetch ends, the signals show it, so the promise resolves with their
|
|
1486
|
+
// data and error and never rejects.
|
|
1487
|
+
refetch: () => untracked(() => {
|
|
1488
|
+
const options = defaultedOptions();
|
|
1489
|
+
return resolve(options.queryKey).fetch(options, {
|
|
1490
|
+
cancelRefetch: true,
|
|
1491
|
+
});
|
|
1492
|
+
})
|
|
1493
|
+
.catch(() => undefined)
|
|
1494
|
+
.then(() => ({ data: result.data(), error: result.error() })),
|
|
1495
|
+
};
|
|
1317
1496
|
return {
|
|
1318
|
-
result
|
|
1319
|
-
data: computed(() => resolved().data),
|
|
1320
|
-
status: computed(() => resolved().status),
|
|
1321
|
-
error: computed(() => query().state().error),
|
|
1322
|
-
isFetching: computed(() => query().state().isFetching),
|
|
1323
|
-
// First fetch in-flight: fetching with no resolved data yet. An active
|
|
1324
|
-
// placeholder counts as data — the UI shows it, not a loading state.
|
|
1325
|
-
isLoading: computed(() => query().state().isFetching && resolved().status === 'pending'),
|
|
1326
|
-
isPending: computed(() => resolved().status === 'pending'),
|
|
1327
|
-
isSuccess: computed(() => resolved().status === 'success'),
|
|
1328
|
-
isError: computed(() => resolved().status === 'error'),
|
|
1329
|
-
isPlaceholderData: computed(() => resolved().isPlaceholderData),
|
|
1330
|
-
failureCount: computed(() => query().state().failureCount),
|
|
1331
|
-
failureReason: computed(() => query().state().failureReason),
|
|
1332
|
-
// Force a fresh fetch regardless of staleTime, cancelling any in-flight
|
|
1333
|
-
// request (explicit user intent — get current data).
|
|
1334
|
-
refetch: () => {
|
|
1335
|
-
const { queryKey, queryFn, retry, retryDelay, structuralSharing } = untracked(defaultedOptions);
|
|
1336
|
-
client.fetchQuery(queryKey, queryFn, {
|
|
1337
|
-
staleTime: 0,
|
|
1338
|
-
retry,
|
|
1339
|
-
retryDelay,
|
|
1340
|
-
structuralSharing,
|
|
1341
|
-
cancelRefetch: true,
|
|
1342
|
-
});
|
|
1343
|
-
},
|
|
1344
|
-
},
|
|
1497
|
+
result,
|
|
1345
1498
|
destroy: () => refs.forEach((ref) => ref.destroy()),
|
|
1346
1499
|
};
|
|
1347
1500
|
}
|
|
@@ -1517,9 +1670,11 @@ function injectQueries(optionsFn, options) {
|
|
|
1517
1670
|
* until it settles. Mutations do not retry by default (a retried write is not
|
|
1518
1671
|
* idempotent); opt in via `options.retry`. Whatever of `retry`, `retryDelay`
|
|
1519
1672
|
* and the `onSuccess` / `onError` / `onSettled` hooks a mutation leaves unset
|
|
1520
|
-
* comes from {@link withDefaultOptions}. `
|
|
1521
|
-
*
|
|
1522
|
-
*
|
|
1673
|
+
* comes from {@link withDefaultOptions}. `mutateAsync(variables)` runs it
|
|
1674
|
+
* the same way and returns a promise of the run's data. `mutate()`,
|
|
1675
|
+
* `mutateAsync()` and `reset()` are safe to call from an effect: none makes
|
|
1676
|
+
* the effect depend on what it reads, such as the options or the previous
|
|
1677
|
+
* run. Bound to the current injection
|
|
1523
1678
|
* context. Destroying it doesn't cancel a run in flight: the write has most
|
|
1524
1679
|
* likely reached the server, so the run finishes and fires its hooks.
|
|
1525
1680
|
*
|
|
@@ -1531,8 +1686,8 @@ function injectQueries(optionsFn, options) {
|
|
|
1531
1686
|
* @typeParam TContext - Type returned by `onMutate`, passed to later hooks.
|
|
1532
1687
|
* @param optionsFn - Factory returning the {@link MutationOptions}.
|
|
1533
1688
|
* @param options - Optional `injector` to use outside an injection context.
|
|
1534
|
-
* @returns A {@link MutationResult}: `mutate()`, `reset()`,
|
|
1535
|
-
* (`status`, `data`, `error`, `isPending`, …).
|
|
1689
|
+
* @returns A {@link MutationResult}: `mutate()`, `mutateAsync()`, `reset()`,
|
|
1690
|
+
* and state signals (`status`, `data`, `error`, `isPending`, …).
|
|
1536
1691
|
*
|
|
1537
1692
|
* @example
|
|
1538
1693
|
* ```ts
|
|
@@ -1564,23 +1719,32 @@ function injectMutation(optionsFn, options) {
|
|
|
1564
1719
|
inject(DestroyRef).onDestroy(() => untracked(current)?.removeObserver());
|
|
1565
1720
|
const state = computed(() => current()?.state() ??
|
|
1566
1721
|
getInitialState(), ...(ngDevMode ? [{ debugName: "state" }] : []));
|
|
1722
|
+
// mutate(), mutateAsync() and reset() run untracked, as the QueryClient's
|
|
1723
|
+
// methods do. A call made from an effect mustn't make the effect depend on
|
|
1724
|
+
// what they read to do their job: the previous run's state, the options,
|
|
1725
|
+
// or what onMutate and mutationFn read. Otherwise a change to any of them
|
|
1726
|
+
// re-runs the effect, and the effect repeats the call.
|
|
1727
|
+
//
|
|
1728
|
+
// Starts a run for mutate() and mutateAsync(), makes it the run the
|
|
1729
|
+
// signals below follow, and returns its promise.
|
|
1730
|
+
const start = (variables) => untracked(() => {
|
|
1731
|
+
// Stop observing the previous run. It isn't cancelled — if it's
|
|
1732
|
+
// still in flight it finishes and fires its hooks; it just no longer
|
|
1733
|
+
// feeds the signals below.
|
|
1734
|
+
current()?.removeObserver();
|
|
1735
|
+
// Options are read per call, so a signal used in them stays live.
|
|
1736
|
+
const run = cache.build(client.defaultMutationOptions(optionsFn()));
|
|
1737
|
+
run.addObserver();
|
|
1738
|
+
current.set(run);
|
|
1739
|
+
return run.execute(variables);
|
|
1740
|
+
});
|
|
1567
1741
|
return {
|
|
1568
|
-
//
|
|
1569
|
-
//
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
// Stop observing the previous run. It isn't cancelled — if it's
|
|
1575
|
-
// still in flight it finishes and fires its hooks; it just no longer
|
|
1576
|
-
// feeds the signals below.
|
|
1577
|
-
current()?.removeObserver();
|
|
1578
|
-
// Options are read per call, so a signal used in them stays live.
|
|
1579
|
-
const run = cache.build(client.defaultMutationOptions(optionsFn()));
|
|
1580
|
-
run.addObserver();
|
|
1581
|
-
current.set(run);
|
|
1582
|
-
run.execute(variables);
|
|
1583
|
-
}),
|
|
1742
|
+
// The outcome shows in the signals; a failure mustn't surface as an
|
|
1743
|
+
// unhandled rejection too.
|
|
1744
|
+
mutate: (variables) => {
|
|
1745
|
+
start(variables).catch(() => undefined);
|
|
1746
|
+
},
|
|
1747
|
+
mutateAsync: start,
|
|
1584
1748
|
reset: () => untracked(() => {
|
|
1585
1749
|
// "Forget the result", not "stop the request". A run still in
|
|
1586
1750
|
// flight keeps going and fires its hooks, so the cache stays in step
|
|
@@ -1773,5 +1937,5 @@ function withDefaultOptions(options) {
|
|
|
1773
1937
|
* Generated bundle index. Do not edit.
|
|
1774
1938
|
*/
|
|
1775
1939
|
|
|
1776
|
-
export { QueryClient, QueryClientFeatureKind, injectIsFetching, injectIsMutating, injectMutation, injectQueries, injectQuery, injectQueryClient, keepPreviousData, mutationOptions, provideQueryClient, queryOptions, withDefaultOptions };
|
|
1940
|
+
export { CancelledError, QueryClient, QueryClientFeatureKind, injectIsFetching, injectIsMutating, injectMutation, injectQueries, injectQuery, injectQueryClient, keepPreviousData, mutationOptions, provideQueryClient, queryOptions, withDefaultOptions };
|
|
1777
1941
|
//# sourceMappingURL=ngx-signal-query.mjs.map
|