-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
mutate.ts
136 lines (117 loc) · 3.98 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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import { serialize } from './serialize'
import { isFunction, isUndefined, mergeObjects, 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<Data>
]
) => {
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`
let populateCache = isUndefined(options.populateCache)
? true
: options.populateCache
const revalidate = options.revalidate !== false
const rollbackOnError = options.rollbackOnError !== false
const customOptimisticData = options.optimisticData
// Serilaize key
const [key, , keyInfo] = serialize(_key)
if (!key) return
const [, , MUTATION] = 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),
UNDEFINED,
UNDEFINED,
revalidate,
true
)
}
let data: any = _data
let error: unknown
// Update global timestamps.
const beforeMutationTs = getTimestamp()
MUTATION[key] = [beforeMutationTs, 0]
const hasCustomOptimisticData = !isUndefined(customOptimisticData)
const rollbackData = cache.get(key)
// Do optimistic data update.
if (hasCustomOptimisticData) {
const optimisticData = isFunction(customOptimisticData)
? customOptimisticData(rollbackData)
: customOptimisticData
cache.set(key, optimisticData)
broadcastState(cache, key, optimisticData)
}
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[key][0]) {
if (error) throw error
return data
} else if (error && hasCustomOptimisticData && rollbackOnError) {
// Rollback. Always populate the cache in this case but without
// transforming the data.
populateCache = true
data = rollbackData
cache.set(key, rollbackData)
}
}
// If we should write back the cache after request.
if (populateCache) {
if (!error) {
// Transform the result into data.
if (isFunction(populateCache)) {
data = populateCache(data, rollbackData)
}
// 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(keyInfo, mergeObjects(cache.get(keyInfo), { error }))
}
// Reset the timestamp to mark the mutation has ended.
MUTATION[key][1] = 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
}