-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
mutate.ts
110 lines (94 loc) · 2.99 KB
/
mutate.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
import { serialize } from './serialize'
import { isFunction, UNDEFINED } from './helper'
import { SWRGlobalState, GlobalState } from './global-state'
import { broadcastState } from './broadcast-state'
import { getTimestamp } from './timestamp'
import { Key, Cache, MutatorCallback, MutatorOptions } from '../types'
export const internalMutate = async <Data>(
...args: [
Cache,
Key,
undefined | Data | Promise<Data | undefined> | MutatorCallback<Data>,
undefined | boolean | MutatorOptions
]
) => {
const [cache, _key, _data, _opts] = args
// When passing as a boolean, it's explicitily used to disable/enable
// revalidation.
const options =
typeof _opts === 'boolean' ? { revalidate: _opts } : _opts || {}
// Fallback to `true` if it's not explicitly set to `false`
const revalidate = options.revalidate !== false
const populateCache = options.populateCache !== false
// Serilaize key
const [key, , keyErr] = serialize(_key)
if (!key) return
const [, , MUTATION_TS, MUTATION_END_TS] = SWRGlobalState.get(
cache
) as GlobalState
// If there is no new data provided, revalidate the key with current state.
if (args.length < 3) {
// Revalidate and broadcast state.
return broadcastState(
cache,
key,
cache.get(key),
cache.get(keyErr),
UNDEFINED,
revalidate,
populateCache
)
}
let data: any = _data
let error: unknown
// Update global timestamps.
const beforeMutationTs = (MUTATION_TS[key] = getTimestamp())
MUTATION_END_TS[key] = 0
if (isFunction(data)) {
// `data` is a function, call it passing current cache value.
try {
data = (data as MutatorCallback<Data>)(cache.get(key))
} catch (err) {
// If it throws an error synchronously, we shouldn't update the cache.
error = err
}
}
// `data` is a promise/thenable, resolve the final data first.
if (data && isFunction((data as Promise<Data>).then)) {
// This means that the mutation is async, we need to check timestamps to
// avoid race conditions.
data = await (data as Promise<Data>).catch(err => {
error = err
})
// Check if other mutations have occurred since we've started this mutation.
// If there's a race we don't update cache or broadcast the change,
// just return the data.
if (beforeMutationTs !== MUTATION_TS[key]) {
if (error) throw error
return data
}
}
if (populateCache) {
if (!error) {
// Only update cached data if there's no error. Data can be `undefined` here.
cache.set(key, data)
}
// Always update or reset the error.
cache.set(keyErr, error)
}
// Reset the timestamp to mark the mutation has ended.
MUTATION_END_TS[key] = getTimestamp()
// Update existing SWR Hooks' internal states:
const res = await broadcastState(
cache,
key,
data,
error,
UNDEFINED,
revalidate,
populateCache
)
// Throw error or return data
if (error) throw error
return populateCache ? res : data
}