handy online tools for AT Protocol boat.kelinci.net
atproto bluesky atcute typescript solidjs
at trunk 129 lines 2.6 kB view raw
1import { createRenderEffect, createSignal } from 'solid-js'; 2 3import { makeAbortable } from './abortable'; 4import { dequal } from './dequal'; 5 6export interface SuccessQueryReturn<R> { 7 data: R; 8 error: undefined; 9 isSuccess: true; 10 isError: false; 11 isPending: false; 12 isIdle: false; 13 refetch(): void; 14} 15export interface ErrorQueryReturn { 16 data: undefined; 17 error: unknown; 18 isSuccess: false; 19 isError: true; 20 isPending: false; 21 isIdle: false; 22 refetch(): void; 23} 24export interface PendingQueryReturn { 25 data: undefined; 26 error: undefined; 27 isSuccess: false; 28 isError: false; 29 isPending: true; 30 isIdle: false; 31 refetch(): void; 32} 33export interface IdleQueryReturn { 34 data: undefined; 35 error: undefined; 36 isSuccess: false; 37 isError: false; 38 isPending: false; 39 isIdle: true; 40 refetch(): void; 41} 42 43export type QueryReturn<R> = SuccessQueryReturn<R> | ErrorQueryReturn | PendingQueryReturn; 44 45const enum QueryState { 46 IDLE, 47 PENDING, 48 SUCCESS, 49 ERROR, 50} 51 52const UNSET_QUERY_KEY = Symbol(); 53 54export const createQuery = <K, R>( 55 keyFn: () => K | null | undefined, 56 queryFn: (key: K, signal: AbortSignal) => Promise<R>, 57): QueryReturn<R> => { 58 let currKey: any = UNSET_QUERY_KEY; 59 60 const [getSignal, cleanup] = makeAbortable(); 61 const [state, setState] = createSignal<{ s: QueryState; d?: any; e?: any }>( 62 { s: QueryState.IDLE }, 63 { equals: (prev, next) => prev.s === next.s }, 64 ); 65 66 const refetch = (force: boolean) => { 67 const nextKey = keyFn(); 68 const prevKey = currKey; 69 currKey = nextKey; 70 71 if (nextKey == null) { 72 cleanup(); 73 setState({ s: QueryState.IDLE }); 74 } else if (force || !dequal(nextKey, prevKey)) { 75 setState({ s: QueryState.PENDING }); 76 77 const signal = getSignal(); 78 79 new Promise((resolve) => resolve(queryFn(nextKey, signal))).then( 80 (data) => { 81 if (signal.aborted) { 82 return; 83 } 84 85 setState({ s: QueryState.SUCCESS, d: data }); 86 }, 87 (err) => { 88 if (signal.aborted) { 89 return; 90 } 91 92 setState({ s: QueryState.ERROR, e: err }); 93 }, 94 ); 95 } 96 }; 97 98 createRenderEffect(() => refetch(false)); 99 100 return { 101 get data() { 102 const $state = state(); 103 if ($state.s === QueryState.SUCCESS) { 104 return $state.d; 105 } 106 }, 107 get error() { 108 const $state = state(); 109 if ($state.s === QueryState.ERROR) { 110 return $state.e; 111 } 112 }, 113 get isSuccess() { 114 return state().s === QueryState.SUCCESS; 115 }, 116 get isError() { 117 return state().s === QueryState.ERROR; 118 }, 119 get isPending() { 120 return state().s === QueryState.PENDING; 121 }, 122 get isIdle() { 123 return state().s === QueryState.IDLE; 124 }, 125 refetch() { 126 refetch(true); 127 }, 128 } as any; 129};