handy online tools for AT Protocol boat.kelinci.net
atproto bluesky atcute typescript solidjs
at trunk 152 lines 3.4 kB view raw
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 = () => {};