-
-
Notifications
You must be signed in to change notification settings - Fork 617
/
atomWithSubscription.ts
111 lines (104 loc) · 3.31 KB
/
atomWithSubscription.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import type {
Client,
OperationContext,
OperationResult,
TypedDocumentNode,
} from '@urql/core'
import { pipe, subscribe } from 'wonka'
import { atom } from 'jotai'
import type { Atom, Getter } from 'jotai'
import { clientAtom } from './clientAtom'
type OperationResultWithData<Data, Variables> = OperationResult<
Data,
Variables
> & {
data: Data
}
const isOperationResultWithData = <Data, Variables>(
result: OperationResult<Data, Variables>
): result is OperationResultWithData<Data, Variables> => 'data' in result
type SubscriptionArgs<Data, Variables extends object> = {
query: TypedDocumentNode<Data, Variables> | string
variables?: Variables
context?: Partial<OperationContext>
}
type SubscriptionArgsWithPause<
Data,
Variables extends object
> = SubscriptionArgs<Data, Variables> & {
pause: boolean
}
export function atomWithSubscription<Data, Variables extends object>(
createSubscriptionArgs: (get: Getter) => SubscriptionArgs<Data, Variables>,
getClient?: (get: Getter) => Client
): Atom<OperationResultWithData<Data, Variables>>
export function atomWithSubscription<Data, Variables extends object>(
createSubscriptionArgs: (
get: Getter
) => SubscriptionArgsWithPause<Data, Variables>,
getClient?: (get: Getter) => Client
): Atom<OperationResultWithData<Data, Variables> | null>
export function atomWithSubscription<Data, Variables extends object>(
createSubscriptionArgs: (get: Getter) => SubscriptionArgs<Data, Variables>,
getClient: (get: Getter) => Client = (get) => get(clientAtom)
) {
const queryResultAtom = atom((get) => {
const args = createSubscriptionArgs(get)
if ((args as { pause?: boolean }).pause) {
return { args }
}
const client = getClient(get)
let resolve: ((result: OperationResult<Data, Variables>) => void) | null =
null
const resultAtom = atom<
| OperationResult<Data, Variables>
| Promise<OperationResult<Data, Variables>>
>(
new Promise<OperationResult<Data, Variables>>((r) => {
resolve = r
})
)
let setResult: (result: OperationResult<Data, Variables>) => void = () => {
throw new Error('setting result without mount')
}
const listener = (result: OperationResult<Data, Variables>) => {
if (!isOperationResultWithData(result)) {
throw new Error('result does not have data')
}
if (resolve) {
resolve(result)
resolve = null
} else {
setResult(result)
}
}
const subscriptionInRender = pipe(
client.subscription(args.query, args.variables, args.context),
subscribe(listener)
)
let timer: NodeJS.Timeout | null = setTimeout(() => {
timer = null
subscriptionInRender.unsubscribe()
}, 1000)
resultAtom.onMount = (update) => {
setResult = update
let subscription: typeof subscriptionInRender
if (timer) {
clearTimeout(timer)
subscription = subscriptionInRender
} else {
subscription = pipe(
client.subscription(args.query, args.variables, args.context),
subscribe(listener)
)
}
return () => subscription.unsubscribe()
}
return { resultAtom, args }
})
const queryAtom = atom((get) => {
const { resultAtom } = get(queryResultAtom)
return resultAtom ? get(resultAtom) : null
})
return queryAtom
}