floppy-disk 3.0.0-alpha.5 → 3.0.0-alpha.6
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/README.md +373 -1
- package/esm/react/create-mutation.d.mts +37 -21
- package/esm/react/create-query.d.mts +24 -22
- package/esm/react/use-mutation.d.mts +82 -0
- package/esm/react.d.mts +2 -1
- package/esm/react.mjs +135 -14
- package/package.json +1 -1
- package/react/create-mutation.d.ts +37 -21
- package/react/create-query.d.ts +24 -22
- package/react/use-mutation.d.ts +82 -0
- package/react.d.ts +2 -1
- package/react.js +134 -12
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type MutationOptions, type MutationState } from './create-mutation.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* A hook for managing async mutation state.
|
|
4
|
+
*
|
|
5
|
+
* @param mutationFn - Async function that performs the mutation.
|
|
6
|
+
* Receives the input variable and the state snapshot before execution.
|
|
7
|
+
*
|
|
8
|
+
* @param options - Optional lifecycle callbacks:
|
|
9
|
+
* - `onSuccess(data, variable, stateBeforeExecute)`
|
|
10
|
+
* - `onError(error, variable, stateBeforeExecute)`
|
|
11
|
+
* - `onSettled(variable, stateBeforeExecute)`
|
|
12
|
+
*
|
|
13
|
+
* @returns A tuple containing:
|
|
14
|
+
* - state: The current mutation state (render snapshot)
|
|
15
|
+
* - controls: An object with mutation actions and helpers
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* - No retry mechanism is provided by default.
|
|
19
|
+
* - The mutation always resolves (never throws): the result contains either `data` or `error`.
|
|
20
|
+
* - If multiple executions triggered at the same time:
|
|
21
|
+
* - Only the latest execution is allowed to update the state.
|
|
22
|
+
* - Results from previous executions are ignored if a newer one exists.
|
|
23
|
+
*/
|
|
24
|
+
export declare const useMutation: <TData, TVariable = undefined, TError = Error>(
|
|
25
|
+
/**
|
|
26
|
+
* Async function that performs the mutation.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* - Does NOT need to be memoized (e.g. `useCallback`).
|
|
30
|
+
* - The latest function reference is always used internally.
|
|
31
|
+
*/
|
|
32
|
+
mutationFn: (variable: TVariable, stateBeforeExecute: MutationState<TData, TVariable, TError>) => Promise<TData>,
|
|
33
|
+
/**
|
|
34
|
+
* Optional lifecycle callbacks.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* - Callbacks do NOT need to be memoized.
|
|
38
|
+
* - The latest callbacks are always used internally.
|
|
39
|
+
*/
|
|
40
|
+
options?: MutationOptions<TData, TVariable, TError>) => [MutationState<TData, TVariable, TError>, {
|
|
41
|
+
/**
|
|
42
|
+
* Executes the mutation.
|
|
43
|
+
*
|
|
44
|
+
* @param variable - Input passed to the mutation function
|
|
45
|
+
*
|
|
46
|
+
* @returns A promise that always resolves with:
|
|
47
|
+
* - `{ data, variable }` on success
|
|
48
|
+
* - `{ error, variable }` on failure
|
|
49
|
+
*
|
|
50
|
+
* @remarks
|
|
51
|
+
* - The promise never rejects to simplify async handling.
|
|
52
|
+
* - If a mutation is already in progress, a warning is logged.
|
|
53
|
+
* - When a new execution starts, all previous pending executions will resolve with the result of the latest execution.
|
|
54
|
+
*/
|
|
55
|
+
execute: TVariable extends undefined ? () => Promise<{
|
|
56
|
+
variable: undefined;
|
|
57
|
+
data?: TData;
|
|
58
|
+
error?: TError;
|
|
59
|
+
}> : (variable: TVariable) => Promise<{
|
|
60
|
+
variable: TVariable;
|
|
61
|
+
data?: TData;
|
|
62
|
+
error?: TError;
|
|
63
|
+
}>;
|
|
64
|
+
/**
|
|
65
|
+
* Resets the mutation state back to its initial state.
|
|
66
|
+
*
|
|
67
|
+
* @remarks
|
|
68
|
+
* - Does not cancel any ongoing execution.
|
|
69
|
+
* - If an execution is still pending, its result may override the reset state.
|
|
70
|
+
*/
|
|
71
|
+
reset: () => void;
|
|
72
|
+
/**
|
|
73
|
+
* Returns the latest mutation state directly from the internal ref.
|
|
74
|
+
*
|
|
75
|
+
* @returns The most up-to-date mutation state.
|
|
76
|
+
*
|
|
77
|
+
* @remarks
|
|
78
|
+
* - Unlike the `state` returned by the hook, this value is not tied to React render cycles.
|
|
79
|
+
* - Use this inside async flows or event handlers to avoid stale reads.
|
|
80
|
+
*/
|
|
81
|
+
getLatestState: () => MutationState<TData, TVariable, TError>;
|
|
82
|
+
}];
|
package/esm/react.d.mts
CHANGED
|
@@ -3,4 +3,5 @@ export { useStoreState } from './react/use-store.mjs';
|
|
|
3
3
|
export * from './react/create-store.mjs';
|
|
4
4
|
export * from './react/create-stores.mjs';
|
|
5
5
|
export * from './react/create-query.mjs';
|
|
6
|
-
export
|
|
6
|
+
export { createMutation, type MutationOptions, type MutationState, } from './react/create-mutation.mjs';
|
|
7
|
+
export * from './react/use-mutation.mjs';
|
package/esm/react.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useLayoutEffect, useEffect, useState, useRef, useMemo } from 'react';
|
|
1
|
+
import { useLayoutEffect, useEffect, useState, useRef, useMemo, useCallback } from 'react';
|
|
2
2
|
import { isClient, initStore, getHash, noop } from 'floppy-disk/vanilla';
|
|
3
3
|
|
|
4
4
|
const useIsomorphicLayoutEffect = isClient ? useLayoutEffect : useEffect;
|
|
@@ -128,7 +128,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
128
128
|
onSettled = noop,
|
|
129
129
|
shouldRetry: shouldRetryFn = (_, s) => s.retryCount === 0 ? [true, 1500] : [false]
|
|
130
130
|
} = options;
|
|
131
|
-
const initialState = INITIAL_STATE$1;
|
|
131
|
+
const initialState = { ...INITIAL_STATE$1 };
|
|
132
132
|
const stores = /* @__PURE__ */ new Map();
|
|
133
133
|
const configureStoreEvents = () => ({
|
|
134
134
|
...options,
|
|
@@ -367,7 +367,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
367
367
|
internals.set(store, configureInternals(store, variable, variableHash));
|
|
368
368
|
}
|
|
369
369
|
const useStore = (options2 = {}) => {
|
|
370
|
-
const {
|
|
370
|
+
const { revalidateOnMount = true, keepPreviousData } = options2;
|
|
371
371
|
const storeState = store.getState();
|
|
372
372
|
const prevState = useRef({});
|
|
373
373
|
let storeStateToBeUsed = storeState;
|
|
@@ -380,7 +380,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
380
380
|
storeStateToBeUsed = { ...storeState, ...prevState.current };
|
|
381
381
|
}
|
|
382
382
|
const [trackedState, usedPathsRef] = useStoreStateProxy(
|
|
383
|
-
|
|
383
|
+
revalidateOnMount && storeState.state === "INITIAL" ? (
|
|
384
384
|
// Optimize rendering on initial state
|
|
385
385
|
// Do { isPending: true } → result
|
|
386
386
|
// instead of { isPending: false } → { isPending: true } → result
|
|
@@ -404,8 +404,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
404
404
|
});
|
|
405
405
|
}, [store]);
|
|
406
406
|
useIsomorphicLayoutEffect(() => {
|
|
407
|
-
if (
|
|
408
|
-
}, [store,
|
|
407
|
+
if (revalidateOnMount !== false) revalidate(store, variable, false);
|
|
408
|
+
}, [store, revalidateOnMount]);
|
|
409
409
|
if (keepPreviousData) {
|
|
410
410
|
!!trackedState.error;
|
|
411
411
|
}
|
|
@@ -479,19 +479,26 @@ const INITIAL_STATE = {
|
|
|
479
479
|
};
|
|
480
480
|
const createMutation = (mutationFn, options = {}) => {
|
|
481
481
|
const { onSuccess = noop, onError, onSettled = noop } = options;
|
|
482
|
-
const initialState = INITIAL_STATE;
|
|
482
|
+
const initialState = { ...INITIAL_STATE };
|
|
483
|
+
let ongoingPromise;
|
|
484
|
+
const resolveFns = /* @__PURE__ */ new Set([]);
|
|
483
485
|
const store = initStore(initialState, options);
|
|
484
486
|
const useStore = () => useStoreState(store.getState(), store.subscribe);
|
|
485
487
|
const execute = (variable) => {
|
|
488
|
+
let currentResolveFn;
|
|
486
489
|
const stateBeforeExecute = store.getState();
|
|
487
490
|
if (stateBeforeExecute.isPending) {
|
|
488
491
|
console.warn(
|
|
489
|
-
"
|
|
492
|
+
"A mutation was executed while a previous execution is still pending. The previous execution will be ignored (latest execution wins)."
|
|
490
493
|
);
|
|
491
494
|
}
|
|
492
495
|
store.setState({ isPending: true });
|
|
493
|
-
|
|
496
|
+
const promise = new Promise((resolve) => {
|
|
497
|
+
currentResolveFn = resolve;
|
|
494
498
|
mutationFn(variable, stateBeforeExecute).then((data) => {
|
|
499
|
+
if (promise !== ongoingPromise) {
|
|
500
|
+
return resolve({ data, variable });
|
|
501
|
+
}
|
|
495
502
|
store.setState({
|
|
496
503
|
state: "SUCCESS",
|
|
497
504
|
isPending: false,
|
|
@@ -504,8 +511,12 @@ const createMutation = (mutationFn, options = {}) => {
|
|
|
504
511
|
errorUpdatedAt: void 0
|
|
505
512
|
});
|
|
506
513
|
resolve({ data, variable });
|
|
514
|
+
resolveFns.clear();
|
|
507
515
|
onSuccess(data, variable, stateBeforeExecute);
|
|
508
516
|
}).catch((error) => {
|
|
517
|
+
if (promise !== ongoingPromise) {
|
|
518
|
+
return resolve({ error, variable });
|
|
519
|
+
}
|
|
509
520
|
store.setState({
|
|
510
521
|
state: "ERROR",
|
|
511
522
|
isPending: false,
|
|
@@ -518,12 +529,19 @@ const createMutation = (mutationFn, options = {}) => {
|
|
|
518
529
|
errorUpdatedAt: Date.now()
|
|
519
530
|
});
|
|
520
531
|
resolve({ error, variable });
|
|
532
|
+
resolveFns.clear();
|
|
521
533
|
if (onError) onError(error, variable, stateBeforeExecute);
|
|
522
534
|
else console.error(store.getState());
|
|
523
535
|
}).finally(() => {
|
|
536
|
+
if (promise !== ongoingPromise) return;
|
|
524
537
|
onSettled(variable, stateBeforeExecute);
|
|
538
|
+
ongoingPromise = void 0;
|
|
525
539
|
});
|
|
526
540
|
});
|
|
541
|
+
if (ongoingPromise) resolveFns.forEach((resolveFn) => resolveFn(promise));
|
|
542
|
+
resolveFns.add(currentResolveFn);
|
|
543
|
+
ongoingPromise = promise;
|
|
544
|
+
return promise;
|
|
527
545
|
};
|
|
528
546
|
return Object.assign(useStore, {
|
|
529
547
|
subscribe: store.subscribe,
|
|
@@ -550,17 +568,17 @@ const createMutation = (mutationFn, options = {}) => {
|
|
|
550
568
|
* - `{ error, variable }` on failure
|
|
551
569
|
*
|
|
552
570
|
* @remarks
|
|
553
|
-
* - If a mutation is already in progress, a warning is logged.
|
|
554
|
-
* - Concurrent executions are allowed but may lead to race conditions.
|
|
555
571
|
* - The promise never rejects to simplify async handling.
|
|
572
|
+
* - If a mutation is already in progress, a warning is logged.
|
|
573
|
+
* - When a new execution starts, all previous pending executions will resolve with the result of the latest execution.
|
|
556
574
|
*/
|
|
557
575
|
execute,
|
|
558
576
|
/**
|
|
559
577
|
* Resets the mutation state back to its initial state.
|
|
560
578
|
*
|
|
561
579
|
* @remarks
|
|
562
|
-
* - Does not cancel any ongoing
|
|
563
|
-
* - If
|
|
580
|
+
* - Does not cancel any ongoing execution.
|
|
581
|
+
* - If an execution is still pending, its result may override the reset state.
|
|
564
582
|
*/
|
|
565
583
|
reset: () => {
|
|
566
584
|
if (store.getState().isPending) {
|
|
@@ -573,4 +591,107 @@ const createMutation = (mutationFn, options = {}) => {
|
|
|
573
591
|
});
|
|
574
592
|
};
|
|
575
593
|
|
|
576
|
-
|
|
594
|
+
const useMutation = (mutationFn, options = {}) => {
|
|
595
|
+
const { onSuccess = noop, onError, onSettled = noop } = options;
|
|
596
|
+
const callbackRef = useRef({ onSuccess, onError, onSettled });
|
|
597
|
+
callbackRef.current.onSuccess = onSuccess;
|
|
598
|
+
callbackRef.current.onError = onError;
|
|
599
|
+
callbackRef.current.onSettled = onSettled;
|
|
600
|
+
const stateRef = useRef({ ...INITIAL_STATE });
|
|
601
|
+
const [, reRender] = useState({});
|
|
602
|
+
const refs = useRef({
|
|
603
|
+
mutationFn,
|
|
604
|
+
ongoingPromise: void 0,
|
|
605
|
+
resolveFns: /* @__PURE__ */ new Set()
|
|
606
|
+
});
|
|
607
|
+
refs.current.mutationFn = mutationFn;
|
|
608
|
+
const execute = useCallback((variable) => {
|
|
609
|
+
let currentResolveFn;
|
|
610
|
+
const stateBeforeExecute = stateRef.current;
|
|
611
|
+
if (stateBeforeExecute.isPending) {
|
|
612
|
+
console.warn(
|
|
613
|
+
"A mutation was executed while a previous execution is still pending. The previous execution will be ignored (latest execution wins)."
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
stateRef.current.isPending = true;
|
|
617
|
+
reRender({});
|
|
618
|
+
const promise = new Promise(
|
|
619
|
+
(resolve) => {
|
|
620
|
+
currentResolveFn = resolve;
|
|
621
|
+
refs.current.mutationFn(variable, stateBeforeExecute).then((data) => {
|
|
622
|
+
if (promise !== refs.current.ongoingPromise) {
|
|
623
|
+
return resolve({ data, variable });
|
|
624
|
+
}
|
|
625
|
+
stateRef.current = {
|
|
626
|
+
state: "SUCCESS",
|
|
627
|
+
isPending: false,
|
|
628
|
+
isSuccess: true,
|
|
629
|
+
isError: false,
|
|
630
|
+
variable,
|
|
631
|
+
data,
|
|
632
|
+
dataUpdatedAt: Date.now(),
|
|
633
|
+
error: void 0,
|
|
634
|
+
errorUpdatedAt: void 0
|
|
635
|
+
};
|
|
636
|
+
reRender({});
|
|
637
|
+
resolve({ data, variable });
|
|
638
|
+
refs.current.resolveFns.clear();
|
|
639
|
+
callbackRef.current.onSuccess(data, variable, stateBeforeExecute);
|
|
640
|
+
}).catch((error) => {
|
|
641
|
+
if (promise !== refs.current.ongoingPromise) {
|
|
642
|
+
return resolve({ error, variable });
|
|
643
|
+
}
|
|
644
|
+
stateRef.current = {
|
|
645
|
+
state: "ERROR",
|
|
646
|
+
isPending: false,
|
|
647
|
+
isSuccess: false,
|
|
648
|
+
isError: true,
|
|
649
|
+
variable,
|
|
650
|
+
data: void 0,
|
|
651
|
+
dataUpdatedAt: void 0,
|
|
652
|
+
error,
|
|
653
|
+
errorUpdatedAt: Date.now()
|
|
654
|
+
};
|
|
655
|
+
reRender({});
|
|
656
|
+
resolve({ error, variable });
|
|
657
|
+
refs.current.resolveFns.clear();
|
|
658
|
+
if (callbackRef.current.onError) {
|
|
659
|
+
callbackRef.current.onError(error, variable, stateBeforeExecute);
|
|
660
|
+
} else {
|
|
661
|
+
console.error(stateRef.current);
|
|
662
|
+
}
|
|
663
|
+
}).finally(() => {
|
|
664
|
+
if (promise !== refs.current.ongoingPromise) return;
|
|
665
|
+
callbackRef.current.onSettled(variable, stateBeforeExecute);
|
|
666
|
+
refs.current.ongoingPromise = void 0;
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
);
|
|
670
|
+
if (refs.current.ongoingPromise) {
|
|
671
|
+
refs.current.resolveFns.forEach((resolveFn) => resolveFn(promise));
|
|
672
|
+
}
|
|
673
|
+
refs.current.resolveFns.add(currentResolveFn);
|
|
674
|
+
refs.current.ongoingPromise = promise;
|
|
675
|
+
return promise;
|
|
676
|
+
}, []);
|
|
677
|
+
const reset = useCallback(() => {
|
|
678
|
+
if (stateRef.current.isPending) {
|
|
679
|
+
console.warn(
|
|
680
|
+
"Mutation state was reset while a request is still pending. The request will continue, but its result may override the reset state."
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
stateRef.current = { ...INITIAL_STATE };
|
|
684
|
+
reRender({});
|
|
685
|
+
}, []);
|
|
686
|
+
const r = [
|
|
687
|
+
stateRef.current,
|
|
688
|
+
{
|
|
689
|
+
execute,
|
|
690
|
+
reset,
|
|
691
|
+
getLatestState: () => stateRef.current
|
|
692
|
+
}
|
|
693
|
+
];
|
|
694
|
+
return r;
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
export { createMutation, createQuery, createStore, createStores, useIsomorphicLayoutEffect, useMutation, useStoreState };
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@ import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
|
|
|
14
14
|
* - No retry mechanism
|
|
15
15
|
* - No caching across executions
|
|
16
16
|
*/
|
|
17
|
-
export type MutationState<TData, TVariable> = {
|
|
17
|
+
export type MutationState<TData, TVariable, TError> = {
|
|
18
18
|
isPending: boolean;
|
|
19
19
|
} & ({
|
|
20
20
|
state: 'INITIAL';
|
|
@@ -41,28 +41,42 @@ export type MutationState<TData, TVariable> = {
|
|
|
41
41
|
variable: TVariable;
|
|
42
42
|
data: undefined;
|
|
43
43
|
dataUpdatedAt: undefined;
|
|
44
|
-
error:
|
|
44
|
+
error: TError;
|
|
45
45
|
errorUpdatedAt: number;
|
|
46
46
|
});
|
|
47
|
+
export declare const INITIAL_STATE: {
|
|
48
|
+
state: string;
|
|
49
|
+
isPending: boolean;
|
|
50
|
+
isSuccess: boolean;
|
|
51
|
+
isError: boolean;
|
|
52
|
+
variable: undefined;
|
|
53
|
+
data: undefined;
|
|
54
|
+
dataUpdatedAt: undefined;
|
|
55
|
+
error: undefined;
|
|
56
|
+
errorUpdatedAt: undefined;
|
|
57
|
+
};
|
|
47
58
|
/**
|
|
48
59
|
* Configuration options for a mutation.
|
|
49
60
|
*
|
|
50
61
|
* @remarks
|
|
51
62
|
* Lifecycle callbacks are triggered for each execution.
|
|
52
63
|
*/
|
|
53
|
-
export type MutationOptions<TData, TVariable> = InitStoreOptions<MutationState<TData, TVariable>> & {
|
|
64
|
+
export type MutationOptions<TData, TVariable, TError = Error> = InitStoreOptions<MutationState<TData, TVariable, TError>> & {
|
|
54
65
|
/**
|
|
55
|
-
* Called when the mutation succeeds
|
|
66
|
+
* Called when the mutation succeeds.\
|
|
67
|
+
* If multiple concurrent executions happened, only the latest execution triggers this callback.
|
|
56
68
|
*/
|
|
57
|
-
onSuccess?: (data: TData, variable: TVariable, stateBeforeExecute: MutationState<TData, TVariable>) => void;
|
|
69
|
+
onSuccess?: (data: TData, variable: TVariable, stateBeforeExecute: MutationState<TData, TVariable, TError>) => void;
|
|
58
70
|
/**
|
|
59
|
-
* Called when the mutation fails
|
|
71
|
+
* Called when the mutation fails.\
|
|
72
|
+
* If multiple concurrent executions happened, only the latest execution triggers this callback.
|
|
60
73
|
*/
|
|
61
|
-
onError?: (error:
|
|
74
|
+
onError?: (error: TError, variable: TVariable, stateBeforeExecute: MutationState<TData, TVariable, TError>) => void;
|
|
62
75
|
/**
|
|
63
|
-
* Called after the mutation settles (either success or error)
|
|
76
|
+
* Called after the mutation settles (either success or error).\
|
|
77
|
+
* If multiple concurrent executions happened, only the latest execution triggers this callback.
|
|
64
78
|
*/
|
|
65
|
-
onSettled?: (variable: TVariable, stateBeforeExecute: MutationState<TData, TVariable>) => void;
|
|
79
|
+
onSettled?: (variable: TVariable, stateBeforeExecute: MutationState<TData, TVariable, TError>) => void;
|
|
66
80
|
};
|
|
67
81
|
/**
|
|
68
82
|
* Creates a mutation store for handling async operations that modify data.
|
|
@@ -78,8 +92,10 @@ export type MutationOptions<TData, TVariable> = InitStoreOptions<MutationState<T
|
|
|
78
92
|
* - Mutations are **not cached** and only track the latest execution.
|
|
79
93
|
* - Designed for operations that change data (e.g. create, update, delete).
|
|
80
94
|
* - No retry mechanism is provided by default.
|
|
81
|
-
* - Each execution overwrites the previous state.
|
|
82
95
|
* - The mutation always resolves (never throws): the result contains either `data` or `error`.
|
|
96
|
+
* - If multiple executions triggered at the same time:
|
|
97
|
+
* - Only the latest execution is allowed to update the state.
|
|
98
|
+
* - Results from previous executions are ignored if a newer one exists.
|
|
83
99
|
*
|
|
84
100
|
* @example
|
|
85
101
|
* const useCreateUser = createMutation(async (input) => {
|
|
@@ -89,10 +105,10 @@ export type MutationOptions<TData, TVariable> = InitStoreOptions<MutationState<T
|
|
|
89
105
|
* const { isPending } = useCreateUser();
|
|
90
106
|
* const result = await useCreateUser.execute({ name: 'John' });
|
|
91
107
|
*/
|
|
92
|
-
export declare const createMutation: <TData, TVariable = undefined>(mutationFn: (variable: TVariable, stateBeforeExecute: MutationState<TData, TVariable>) => Promise<TData>, options?: MutationOptions<TData, TVariable>) => (() => MutationState<TData, TVariable>) & {
|
|
93
|
-
subscribe: (subscriber: import("../vanilla.ts").Subscriber<MutationState<TData, TVariable>>) => () => void;
|
|
94
|
-
getSubscribers: () => Set<import("../vanilla.ts").Subscriber<MutationState<TData, TVariable>>>;
|
|
95
|
-
getState: () => MutationState<TData, TVariable>;
|
|
108
|
+
export declare const createMutation: <TData, TVariable = undefined, TError = Error>(mutationFn: (variable: TVariable, stateBeforeExecute: MutationState<TData, TVariable, TError>) => Promise<TData>, options?: MutationOptions<TData, TVariable, TError>) => (() => MutationState<TData, TVariable, TError>) & {
|
|
109
|
+
subscribe: (subscriber: import("../vanilla.ts").Subscriber<MutationState<TData, TVariable, TError>>) => () => void;
|
|
110
|
+
getSubscribers: () => Set<import("../vanilla.ts").Subscriber<MutationState<TData, TVariable, TError>>>;
|
|
111
|
+
getState: () => MutationState<TData, TVariable, TError>;
|
|
96
112
|
/**
|
|
97
113
|
* Manually updates the mutation state.
|
|
98
114
|
*
|
|
@@ -100,7 +116,7 @@ export declare const createMutation: <TData, TVariable = undefined>(mutationFn:
|
|
|
100
116
|
* - Intended for advanced use cases.
|
|
101
117
|
* - Prefer using provided mutation actions (`execute`, `reset`) instead.
|
|
102
118
|
*/
|
|
103
|
-
setState: (value: SetState<MutationState<TData, TVariable>>) => void;
|
|
119
|
+
setState: (value: SetState<MutationState<TData, TVariable, TError>>) => void;
|
|
104
120
|
/**
|
|
105
121
|
* Executes the mutation.
|
|
106
122
|
*
|
|
@@ -111,25 +127,25 @@ export declare const createMutation: <TData, TVariable = undefined>(mutationFn:
|
|
|
111
127
|
* - `{ error, variable }` on failure
|
|
112
128
|
*
|
|
113
129
|
* @remarks
|
|
114
|
-
* - If a mutation is already in progress, a warning is logged.
|
|
115
|
-
* - Concurrent executions are allowed but may lead to race conditions.
|
|
116
130
|
* - The promise never rejects to simplify async handling.
|
|
131
|
+
* - If a mutation is already in progress, a warning is logged.
|
|
132
|
+
* - When a new execution starts, all previous pending executions will resolve with the result of the latest execution.
|
|
117
133
|
*/
|
|
118
134
|
execute: TVariable extends undefined ? () => Promise<{
|
|
119
135
|
variable: undefined;
|
|
120
136
|
data?: TData;
|
|
121
|
-
error?:
|
|
137
|
+
error?: TError;
|
|
122
138
|
}> : (variable: TVariable) => Promise<{
|
|
123
139
|
variable: TVariable;
|
|
124
140
|
data?: TData;
|
|
125
|
-
error?:
|
|
141
|
+
error?: TError;
|
|
126
142
|
}>;
|
|
127
143
|
/**
|
|
128
144
|
* Resets the mutation state back to its initial state.
|
|
129
145
|
*
|
|
130
146
|
* @remarks
|
|
131
|
-
* - Does not cancel any ongoing
|
|
132
|
-
* - If
|
|
147
|
+
* - Does not cancel any ongoing execution.
|
|
148
|
+
* - If an execution is still pending, its result may override the reset state.
|
|
133
149
|
*/
|
|
134
150
|
reset: () => void;
|
|
135
151
|
};
|
package/react/create-query.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ import { type InitStoreOptions, type SetState } from 'floppy-disk/vanilla';
|
|
|
19
19
|
* @remarks
|
|
20
20
|
* - Data and error are mutually exclusive except in `SUCCESS_BUT_REVALIDATION_ERROR`.
|
|
21
21
|
*/
|
|
22
|
-
export type QueryState<TData> = {
|
|
22
|
+
export type QueryState<TData, TError> = {
|
|
23
23
|
isPending: boolean;
|
|
24
24
|
isRevalidating: boolean;
|
|
25
25
|
isRetrying: boolean;
|
|
@@ -46,7 +46,7 @@ export type QueryState<TData> = {
|
|
|
46
46
|
isError: true;
|
|
47
47
|
data: undefined;
|
|
48
48
|
dataUpdatedAt: undefined;
|
|
49
|
-
error:
|
|
49
|
+
error: TError;
|
|
50
50
|
errorUpdatedAt: number;
|
|
51
51
|
} | {
|
|
52
52
|
state: 'SUCCESS_BUT_REVALIDATION_ERROR';
|
|
@@ -54,7 +54,7 @@ export type QueryState<TData> = {
|
|
|
54
54
|
isError: false;
|
|
55
55
|
data: TData;
|
|
56
56
|
dataUpdatedAt: number;
|
|
57
|
-
error:
|
|
57
|
+
error: TError;
|
|
58
58
|
errorUpdatedAt: number;
|
|
59
59
|
});
|
|
60
60
|
/**
|
|
@@ -63,7 +63,7 @@ export type QueryState<TData> = {
|
|
|
63
63
|
* @remarks
|
|
64
64
|
* Controls caching, retry behavior, lifecycle, and side effects of an async operation.
|
|
65
65
|
*/
|
|
66
|
-
export type QueryOptions<TData, TVariable extends Record<string, any
|
|
66
|
+
export type QueryOptions<TData, TVariable extends Record<string, any>, TError = Error> = InitStoreOptions<QueryState<TData, TError>> & {
|
|
67
67
|
/**
|
|
68
68
|
* Time (in milliseconds) that data is considered fresh.
|
|
69
69
|
*
|
|
@@ -95,15 +95,15 @@ export type QueryOptions<TData, TVariable extends Record<string, any>> = InitSto
|
|
|
95
95
|
/**
|
|
96
96
|
* Called when the query succeeds.
|
|
97
97
|
*/
|
|
98
|
-
onSuccess?: (data: TData, variable: TVariable, stateBeforeExecute: QueryState<TData>) => void;
|
|
98
|
+
onSuccess?: (data: TData, variable: TVariable, stateBeforeExecute: QueryState<TData, TError>) => void;
|
|
99
99
|
/**
|
|
100
100
|
* Called when the query fails and will not retry.
|
|
101
101
|
*/
|
|
102
|
-
onError?: (error:
|
|
102
|
+
onError?: (error: TError, variable: TVariable, stateBeforeExecute: QueryState<TData, TError>) => void;
|
|
103
103
|
/**
|
|
104
104
|
* Called after the query settles (success or final failure).
|
|
105
105
|
*/
|
|
106
|
-
onSettled?: (variable: TVariable, stateBeforeExecute: QueryState<TData>) => void;
|
|
106
|
+
onSettled?: (variable: TVariable, stateBeforeExecute: QueryState<TData, TError>) => void;
|
|
107
107
|
/**
|
|
108
108
|
* Determines whether a failed query should retry.
|
|
109
109
|
*
|
|
@@ -120,7 +120,7 @@ export type QueryOptions<TData, TVariable extends Record<string, any>> = InitSto
|
|
|
120
120
|
* return [false];
|
|
121
121
|
* }
|
|
122
122
|
*/
|
|
123
|
-
shouldRetry?: (error:
|
|
123
|
+
shouldRetry?: (error: TError, currentState: QueryState<TData, TError>) => [true, number] | [false];
|
|
124
124
|
};
|
|
125
125
|
/**
|
|
126
126
|
* Creates a query factory for managing cached async operations.
|
|
@@ -157,13 +157,15 @@ export type QueryOptions<TData, TVariable extends Record<string, any>> = InitSto
|
|
|
157
157
|
* // ...
|
|
158
158
|
* }
|
|
159
159
|
*/
|
|
160
|
-
export declare const createQuery: <TData, TVariable extends Record<string, any> = never>(queryFn: (variable: TVariable, currentState: QueryState<TData>) => Promise<TData>, options?: QueryOptions<TData, TVariable>) => ((variable?: TVariable) => ((options?: {
|
|
160
|
+
export declare const createQuery: <TData, TVariable extends Record<string, any> = never, TError = Error>(queryFn: (variable: TVariable, currentState: QueryState<TData, TError>) => Promise<TData>, options?: QueryOptions<TData, TVariable, TError>) => ((variable?: TVariable) => ((options?: {
|
|
161
161
|
/**
|
|
162
|
-
* Whether the query should
|
|
162
|
+
* Whether the query should be ravalidated automatically on mount.
|
|
163
|
+
*
|
|
164
|
+
* Revalidate means execute the queryFn **if stale/invalidated**.
|
|
163
165
|
*
|
|
164
166
|
* @default true
|
|
165
167
|
*/
|
|
166
|
-
|
|
168
|
+
revalidateOnMount?: boolean;
|
|
167
169
|
/**
|
|
168
170
|
* Whether to keep previous successful data while a new variable is loading.
|
|
169
171
|
*
|
|
@@ -180,13 +182,13 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
180
182
|
* // While loading userId=2, still show userId=1 data
|
|
181
183
|
* useQuery({ id: userId }, { keepPreviousData: true });
|
|
182
184
|
*/ keepPreviousData?: boolean;
|
|
183
|
-
}) => QueryState<TData>) & {
|
|
185
|
+
}) => QueryState<TData, TError>) & {
|
|
184
186
|
metadata: {
|
|
185
187
|
isInvalidated?: boolean;
|
|
186
|
-
promise?: Promise<QueryState<TData>> | undefined;
|
|
187
|
-
promiseResolver?: ((value: QueryState<TData> | PromiseLike<QueryState<TData>>) => void) | undefined;
|
|
188
|
+
promise?: Promise<QueryState<TData, TError>> | undefined;
|
|
189
|
+
promiseResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
|
|
188
190
|
retryTimeoutId?: number;
|
|
189
|
-
retryResolver?: ((value: QueryState<TData> | PromiseLike<QueryState<TData>>) => void) | undefined;
|
|
191
|
+
retryResolver?: ((value: QueryState<TData, TError> | PromiseLike<QueryState<TData, TError>>) => void) | undefined;
|
|
190
192
|
garbageCollectionTimeoutId?: number;
|
|
191
193
|
rollbackData?: TData | undefined;
|
|
192
194
|
};
|
|
@@ -222,7 +224,7 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
222
224
|
*/
|
|
223
225
|
execute: (options?: {
|
|
224
226
|
overwriteOngoingExecution?: boolean;
|
|
225
|
-
}) => Promise<QueryState<TData>>;
|
|
227
|
+
}) => Promise<QueryState<TData, TError>>;
|
|
226
228
|
/**
|
|
227
229
|
* Re-executes the query if needed based on freshness or invalidation.
|
|
228
230
|
*
|
|
@@ -238,7 +240,7 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
238
240
|
*/
|
|
239
241
|
revalidate: (options?: {
|
|
240
242
|
overwriteOngoingExecution?: boolean;
|
|
241
|
-
}) => Promise<QueryState<TData>>;
|
|
243
|
+
}) => Promise<QueryState<TData, TError>>;
|
|
242
244
|
/**
|
|
243
245
|
* Marks the query as invalidated and optionally triggers re-execution.
|
|
244
246
|
*
|
|
@@ -290,7 +292,7 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
290
292
|
* const { rollback, revalidate } = query.optimisticUpdate(newData);
|
|
291
293
|
*/
|
|
292
294
|
optimisticUpdate: (data: TData) => {
|
|
293
|
-
revalidate: () => Promise<QueryState<TData>>;
|
|
295
|
+
revalidate: () => Promise<QueryState<TData, TError>>;
|
|
294
296
|
rollback: () => TData;
|
|
295
297
|
};
|
|
296
298
|
/**
|
|
@@ -302,10 +304,10 @@ export declare const createQuery: <TData, TVariable extends Record<string, any>
|
|
|
302
304
|
* - Should be used if an optimistic update fails.
|
|
303
305
|
*/
|
|
304
306
|
rollbackOptimisticUpdate: () => TData;
|
|
305
|
-
subscribe: (subscriber: import("../vanilla.ts").Subscriber<QueryState<TData>>) => () => void;
|
|
306
|
-
getSubscribers: () => Set<import("../vanilla.ts").Subscriber<QueryState<TData>>>;
|
|
307
|
-
getState: () => QueryState<TData>;
|
|
308
|
-
setState: (value: SetState<QueryState<TData>>) => void;
|
|
307
|
+
subscribe: (subscriber: import("../vanilla.ts").Subscriber<QueryState<TData, TError>>) => () => void;
|
|
308
|
+
getSubscribers: () => Set<import("../vanilla.ts").Subscriber<QueryState<TData, TError>>>;
|
|
309
|
+
getState: () => QueryState<TData, TError>;
|
|
310
|
+
setState: (value: SetState<QueryState<TData, TError>>) => void;
|
|
309
311
|
}) & {
|
|
310
312
|
/**
|
|
311
313
|
* Executes all query instances.
|