handy online tools for AT Protocol
boat.kelinci.net
atproto
bluesky
atcute
typescript
solidjs
1import { createSignal } from 'solid-js';
2import { makeAbortable } from './abortable';
3
4export interface SuccessMutationState<D, V> {
5 data: D;
6 error: undefined;
7 variables: V;
8 isSuccess: true;
9 isError: false;
10 isPending: false;
11 isIdle: false;
12}
13
14export interface ErrorMutationState<V> {
15 data: undefined;
16 error: unknown;
17 variables: V;
18 isSuccess: false;
19 isError: true;
20 isPending: false;
21 isIdle: false;
22}
23
24export interface PendingMutationState<V> {
25 data: undefined;
26 error: undefined;
27 variables: V;
28 isSuccess: false;
29 isError: false;
30 isPending: true;
31 isIdle: false;
32}
33
34export interface IdleMutationState {
35 data: undefined;
36 error: undefined;
37 variables: undefined;
38 isSuccess: false;
39 isError: false;
40 isPending: false;
41 isIdle: true;
42}
43
44export type MutationReturn<D, V> = (
45 | IdleMutationState
46 | PendingMutationState<V>
47 | SuccessMutationState<D, V>
48 | ErrorMutationState<V>
49) & {
50 mutate(variables: V): void;
51 mutateAsync(variables: V): Promise<D>;
52 reset(): void;
53};
54
55type MutationFunction<D = unknown, V = unknown> = (variables: V, signal: AbortSignal) => Promise<D>;
56
57export interface MutationOptions<D = unknown, V = unknown> {
58 mutationFn: MutationFunction<D, V>;
59 onMutate?: (variables: V) => void;
60 onSuccess?: (data: NoInfer<D>, variables: NoInfer<V>) => void;
61 onError?: (error: unknown, variables: NoInfer<V>) => void;
62 onSettled?: (data: NoInfer<D> | undefined, error: unknown, variables: NoInfer<V>) => void;
63}
64
65const enum MutationState {
66 IDLE,
67 PENDING,
68 SUCCESS,
69 ERROR,
70}
71
72export const createMutation = <D, V = void>(options: MutationOptions<D, V>): MutationReturn<D, V> => {
73 const [getSignal, cleanup] = makeAbortable();
74 const [state, setState] = createSignal<{ s: MutationState; v?: any; d?: any; e?: any }>(
75 { s: MutationState.IDLE },
76 { equals: (prev, next) => prev.s === next.s },
77 );
78
79 const reset = () => {
80 cleanup();
81 setState({ s: MutationState.IDLE });
82 };
83
84 const mutate = async (variables: V): Promise<D> => {
85 const signal = getSignal();
86
87 setState({ s: MutationState.PENDING, v: variables });
88
89 try {
90 options.onMutate?.(variables);
91
92 const data = await options.mutationFn(variables, signal);
93
94 if (!signal.aborted) {
95 options.onSuccess?.(data, variables);
96 options.onSettled?.(data, undefined, variables);
97
98 setState({ s: MutationState.SUCCESS, v: variables, d: data });
99 }
100
101 return data;
102 } catch (err) {
103 if (!signal.aborted) {
104 options.onError?.(err, variables);
105 options.onSettled?.(undefined, err, variables);
106
107 setState({ s: MutationState.ERROR, v: variables, e: err });
108 }
109
110 throw err;
111 } finally {
112 if (!signal.aborted) {
113 cleanup();
114 }
115 }
116 };
117
118 return {
119 get data() {
120 const $state = state();
121 if ($state.s === MutationState.SUCCESS) {
122 return $state.d;
123 }
124 },
125 get error() {
126 const $state = state();
127 if ($state.s === MutationState.ERROR) {
128 return $state.e;
129 }
130 },
131 get variables() {
132 return state().v;
133 },
134 get isSuccess() {
135 return state().s === MutationState.SUCCESS;
136 },
137 get isError() {
138 return state().s === MutationState.ERROR;
139 },
140 get isPending() {
141 return state().s === MutationState.PENDING;
142 },
143 get isIdle() {
144 return state().s === MutationState.IDLE;
145 },
146 mutateAsync: mutate,
147 mutate: (variables: V) => mutate(variables).then(noop, noop),
148 reset: reset,
149 } as any;
150};
151
152const noop = () => {};