-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
persist.ts
172 lines (155 loc) 路 4.72 KB
/
persist.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import {
QueryClient,
dehydrate,
DehydratedState,
DehydrateOptions,
HydrateOptions,
hydrate,
} from '../core'
import { Promisable } from 'type-fest'
export interface Persister {
persistClient(persistClient: PersistedClient): Promisable<void>
restoreClient(): Promisable<PersistedClient | undefined>
removeClient(): Promisable<void>
}
export interface PersistedClient {
timestamp: number
buster: string
clientState: DehydratedState
}
export interface PersistQueryClienRootOptions {
/** The QueryClient to persist */
queryClient: QueryClient
/** The Persister interface for storing and restoring the cache
* to/from a persisted location */
persister: Persister
/** A unique string that can be used to forcefully
* invalidate existing caches if they do not share the same buster string */
buster?: string
}
export interface PersistedQueryClientRestoreOptions
extends PersistQueryClienRootOptions {
/** The max-allowed age of the cache in milliseconds.
* If a persisted cache is found that is older than this
* time, it will be discarded */
maxAge?: number
/** The options passed to the hydrate function */
hydrateOptions?: HydrateOptions
}
export interface PersistedQueryClientSaveOptions
extends PersistQueryClienRootOptions {
/** The options passed to the dehydrate function */
dehydrateOptions?: DehydrateOptions
}
export interface PersistQueryClientOptions
extends PersistedQueryClientRestoreOptions,
PersistedQueryClientSaveOptions,
PersistQueryClienRootOptions {}
/**
* Restores persisted data to the QueryCache
* - data obtained from persister.restoreClient
* - data is hydrated using hydrateOptions
* If data is expired, busted, empty, or throws, it runs persister.removeClient
*/
export async function persistQueryClientRestore({
queryClient,
persister,
maxAge = 1000 * 60 * 60 * 24,
buster = '',
hydrateOptions,
}: PersistedQueryClientRestoreOptions) {
if (typeof window !== 'undefined') {
try {
const persistedClient = await persister.restoreClient()
if (persistedClient) {
if (persistedClient.timestamp) {
const expired = Date.now() - persistedClient.timestamp > maxAge
const busted = persistedClient.buster !== buster
if (expired || busted) {
persister.removeClient()
} else {
hydrate(queryClient, persistedClient.clientState, hydrateOptions)
}
} else {
persister.removeClient()
}
}
} catch (err) {
if (process.env.NODE_ENV !== 'production') {
queryClient.getLogger().error(err)
queryClient
.getLogger()
.warn(
'Encountered an error attempting to restore client cache from persisted location. As a precaution, the persisted cache will be discarded.'
)
}
persister.removeClient()
}
}
}
/**
* Persists data from the QueryCache
* - data dehydrated using dehydrateOptions
* - data is persisted using persister.persistClient
*/
export async function persistQueryClientSave({
queryClient,
persister,
buster = '',
dehydrateOptions,
}: PersistedQueryClientSaveOptions) {
if (typeof window !== 'undefined') {
const persistClient: PersistedClient = {
buster,
timestamp: Date.now(),
clientState: dehydrate(queryClient, dehydrateOptions),
}
await persister.persistClient(persistClient)
}
}
/**
* Subscribe to QueryCache and MutationCache updates (for persisting)
* @returns an unsubscribe function (to discontinue monitoring)
*/
export function persistQueryClientSubscribe(
props: PersistedQueryClientSaveOptions
) {
const unsubscribeQueryCache = props.queryClient
.getQueryCache()
.subscribe(() => {
persistQueryClientSave(props)
})
const unusbscribeMutationCache = props.queryClient
.getMutationCache()
.subscribe(() => {
persistQueryClientSave(props)
})
return () => {
unsubscribeQueryCache()
unusbscribeMutationCache()
}
}
/**
* Restores persisted data to QueryCache and persists further changes.
*/
export function persistQueryClient(
props: PersistQueryClientOptions
): [() => void, Promise<void>] {
let hasUnsubscribed = false
let persistQueryClientUnsubscribe: (() => void) | undefined
const unsubscribe = () => {
hasUnsubscribed = true
persistQueryClientUnsubscribe?.()
}
let restorePromise = Promise.resolve()
if (typeof window !== 'undefined') {
// Attempt restore
restorePromise = persistQueryClientRestore(props).then(() => {
if (!hasUnsubscribed) {
// Subscribe to changes in the query cache to trigger the save
persistQueryClientUnsubscribe = persistQueryClientSubscribe(props)
}
})
}
return [unsubscribe, restorePromise]
}