-
Notifications
You must be signed in to change notification settings - Fork 0
/
async.ts
113 lines (98 loc) · 3.11 KB
/
async.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
112
113
import type {Rerender} from "./purity.js"
export type QueryStatus = "initial" | "pending" | "success" | "error"
export type QueryCache<T> = {
status: QueryStatus
data?: T
error?: Error
expires: number
}
export type Fire<T> = (options?: {
optimisticData?: T
_shouldRefetch?: boolean
mutation?: (cache: QueryCache<T>) => Promise<void>
}) => Promise<void>
export type Unwrap<T> = () => T | Promise<T>
export type QueryMethods<T> = {
fire: Fire<T>
unwrap: Unwrap<T>
getCached: () => QueryCache<T>
}
export type Call<T> = (optimisticData?: T) => QueryCache<T> & QueryMethods<T>
export type Query<T> = (optimisticData?: T) => Promise<T>
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const makeAsync = (rerender: Rerender) => {
const caches: Record<string, QueryCache<unknown> | undefined> = {}
const _getOrCreateCache = <T>(key: string): QueryCache<T> => {
if (!(key in caches)) {
console.warn(`Creating cache for [${key}] for the first time`)
caches[key] = {status: "initial", expires: 0}
} else {
console.warn(`Cache for [${key}] exists:`, caches[key])
}
return caches[key] as QueryCache<T>
}
return {
useAsync: <T>(
key: string,
query: Query<T>,
{expiration = 3600000} = {}
): QueryMethods<T> & {call: Call<T>} => {
const cache = _getOrCreateCache<T>(key)
const fire: Fire<T> = async ({
optimisticData,
_shouldRefetch,
mutation,
} = {}) => {
console.warn("🔥")
if (cache.status === "pending") {
console.warn("❗ Skipped 🔥 due to race condition.")
return
}
try {
if (optimisticData !== undefined) {
cache.data = optimisticData
}
cache.status = "pending"
if (!_shouldRefetch) {
// TODO: investigate why does it fail without the IF?
rerender()
}
if (mutation) {
await mutation(cache)
}
cache.data = await query(optimisticData)
cache.status = "success"
cache.error = undefined
cache.expires = Date.now() + expiration
} catch (err) {
console.error(`HERE IS THE ERROR:`, err)
cache.status = "error"
cache.error = err as Error
cache.expires = Date.now() + expiration
} finally {
rerender()
}
}
const unwrap: Unwrap<T> = () => {
if (cache.status === "success" && Date.now() < cache.expires) {
return cache.data as T
} else {
return query()
}
}
const getCached = (): QueryCache<T> => cache
const call: Call<T> = optimisticData => {
const _shouldRefetch =
cache.status === "initial" || Date.now() >= cache.expires
if (_shouldRefetch) {
console.warn(
`Executing the query for a newly created or expired cache`
)
fire({optimisticData, _shouldRefetch})
}
return {fire, unwrap, getCached, ...cache}
}
return {call, fire, unwrap, getCached}
},
}
}