-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
mutate.ts
152 lines (132 loc) · 4.38 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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
import { serialize } from './serialize'
import { createCacheHelper, isFunction, isUndefined, UNDEFINED } from './helper'
import { SWRGlobalState } from './global-state'
import { getTimestamp } from './timestamp'
import * as revalidateEvents from '../constants'
import {
Key,
Cache,
MutatorCallback,
MutatorOptions,
GlobalState,
State
} from '../types'
export const internalMutate = async <Data>(
...args: [
Cache,
Key,
undefined | Data | Promise<Data | undefined> | MutatorCallback<Data>,
undefined | boolean | MutatorOptions<Data>
]
): Promise<Data | undefined> => {
const [cache, _key, _data, _opts] = args
// When passing as a boolean, it's explicitly 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
let optimisticData = options.optimisticData
const revalidate = options.revalidate !== false
const rollbackOnError = options.rollbackOnError !== false
// Serialize key
const [key] = serialize(_key)
if (!key) return
const [get, set] = createCacheHelper<
Data,
State<Data, any> & {
// The original data.
_o?: Data
}
>(cache, key)
const [EVENT_REVALIDATORS, MUTATION, FETCH] = SWRGlobalState.get(
cache
) as GlobalState
const revalidators = EVENT_REVALIDATORS[key]
const startRevalidate = () => {
if (revalidate) {
// Invalidate the key by deleting the concurrent request markers so new
// requests will not be deduped.
delete FETCH[key]
if (revalidators && revalidators[0]) {
return revalidators[0](revalidateEvents.MUTATE_EVENT).then(
() => get().data
)
}
}
return get().data
}
// If there is no new data provided, revalidate the key with current state.
if (args.length < 3) {
// Revalidate and broadcast state.
return startRevalidate()
}
let data: any = _data
let error: unknown
// Update global timestamps.
const beforeMutationTs = getTimestamp()
MUTATION[key] = [beforeMutationTs, 0]
const hasOptimisticData = !isUndefined(optimisticData)
const state = get()
const currentData = state.data
const originalData = isUndefined(state._o) ? currentData : state._o
// Do optimistic data update.
if (hasOptimisticData) {
optimisticData = isFunction(optimisticData)
? optimisticData(currentData)
: optimisticData
set({ data: optimisticData, _o: originalData })
}
if (isFunction(data)) {
// `data` is a function, call it passing current cache value.
try {
data = (data as MutatorCallback<Data>)(currentData)
} 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 && hasOptimisticData && rollbackOnError) {
// Rollback. Always populate the cache in this case but without
// transforming the data.
populateCache = true
data = originalData
set({ data: originalData })
}
}
// If we should write back the cache after request.
if (populateCache) {
if (!error) {
// Transform the result into data.
if (isFunction(populateCache)) {
data = populateCache(data, currentData)
}
// Only update cached data if there's no error. Data can be `undefined` here.
set({ data })
}
// Always update or reset the error and original data field.
set({ error, _o: UNDEFINED })
}
// Reset the timestamp to mark the mutation has ended.
MUTATION[key][1] = getTimestamp()
// Update existing SWR Hooks' internal states:
const res = await startRevalidate()
// Throw error or return data
if (error) throw error
return populateCache ? res : data
}