-
-
Notifications
You must be signed in to change notification settings - Fork 116
/
Copy pathindex.ts
285 lines (257 loc) · 9.75 KB
/
index.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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
/**
* Created by championswimmer on 18/07/17.
*/
import { Mutation, MutationPayload, Plugin, Store } from 'vuex'
import { AsyncStorage } from './AsyncStorage'
import { MockStorage } from './MockStorage'
import { PersistOptions } from './PersistOptions'
import SimplePromiseQueue from './SimplePromiseQueue'
import { merge, MergeOptionType } from './utils'
let FlattedJSON = JSON
/**
* A class that implements the vuex persistence.
* @type S type of the 'state' inside the store (default: any)
*/
export class VuexPersistence<S> implements PersistOptions<S> {
public asyncStorage: boolean
public storage: Storage | AsyncStorage | undefined
public restoreState: (key: string, storage?: AsyncStorage | Storage) => Promise<S> | S
public saveState: (key: string, state: {}, storage?: AsyncStorage | Storage) => Promise<void> | void
public reducer: (state: S) => Partial<S>
public key: string
public filter: (mutation: MutationPayload) => boolean
public modules: string[]
public strictMode: boolean
public supportCircular: boolean
public mergeOption: MergeOptionType
/**
* The plugin function that can be used inside a vuex store.
*/
public plugin: Plugin<S>
/**
* A mutation that can be used to restore state
* Helpful if we are running in strict mode
*/
public RESTORE_MUTATION: Mutation<S>
public subscribed: boolean
// tslint:disable-next-line:variable-name
private _mutex = new SimplePromiseQueue()
/**
* Create a {@link VuexPersistence} object.
* Use the <code>plugin</code> function of this class as a
* Vuex plugin.
* @param {PersistOptions} options
*/
public constructor(options?: PersistOptions<S>) {
if (typeof options === 'undefined') options = {} as PersistOptions<S>
this.key = ((options.key != null) ? options.key : 'vuex')
this.subscribed = false
this.supportCircular = options.supportCircular || false
if (this.supportCircular) {
FlattedJSON = require('flatted')
}
this.mergeOption = options.mergeOption || 'replaceArrays'
let localStorageLitmus = true
try {
window.localStorage.getItem('')
} catch (err) {
localStorageLitmus = false
}
/**
* 1. First, prefer storage sent in optinos
* 2. Otherwise, use window.localStorage if available
* 3. Finally, try to use MockStorage
* 4. None of above? Well we gotta fail.
*/
if (options.storage) { this.storage = options.storage }
else if (localStorageLitmus) { this.storage = window.localStorage }
else if (MockStorage) { this.storage = new MockStorage() }
else { throw new Error("Neither 'window' is defined, nor 'MockStorage' is available") }
/**
* How this works is -
* 1. If there is options.reducer function, we use that, if not;
* 2. We check options.modules;
* 1. If there is no options.modules array, we use entire state in reducer
* 2. Otherwise, we create a reducer that merges all those state modules that are
* defined in the options.modules[] array
* @type {((state: S) => {}) | ((state: S) => S) | ((state: any) => {})}
*/
this.reducer = (
(options.reducer != null)
? options.reducer
: (
(options.modules == null)
? ((state: S) => state)
: (
(state: any) =>
(options!.modules as string[]).reduce((a, i) =>
merge(a, { [i]: state[i] }, this.mergeOption), {/* start empty accumulator*/ })
)
)
)
this.filter = options.filter || ((mutation) => true)
this.strictMode = options.strictMode || false
const _this = this
this.RESTORE_MUTATION = function RESTORE_MUTATION(state: S, savedState: any) {
const mergedState = merge(state, savedState || {}, _this.mergeOption)
for (const propertyName of Object.keys(mergedState as {})) {
// Maintain support for vue 2
if ((this as any)._vm !== undefined && (this as any)._vm.$set !== undefined) {
(this as any)._vm.$set(state, propertyName, (mergedState as any)[propertyName])
continue
}
(state as any)[propertyName] = (mergedState as any)[propertyName]
}
}
this.asyncStorage = options.asyncStorage || false
if (this.asyncStorage) {
/**
* Async {@link #VuexPersistence.restoreState} implementation
* @type {((key: string, storage?: Storage) =>
* (Promise<S> | S)) | ((key: string, storage: AsyncStorage) => Promise<any>)}
*/
this.restoreState = (
(options.restoreState != null)
? options.restoreState
: ((key: string, storage: AsyncStorage) =>
(storage).getItem(key)
.then((value) =>
typeof value === 'string' // If string, parse, or else, just return
? (
this.supportCircular
? FlattedJSON.parse(value || '{}')
: JSON.parse(value || '{}')
)
: (value || {})
)
)
)
/**
* Async {@link #VuexPersistence.saveState} implementation
* @type {((key: string, state: {}, storage?: Storage) =>
* (Promise<void> | void)) | ((key: string, state: {}, storage?: Storage) => Promise<void>)}
*/
this.saveState = (
(options.saveState != null)
? options.saveState
: ((key: string, state: {}, storage: AsyncStorage) =>
(storage).setItem(
key, // Second argument is state _object_ if asyc storage, stringified otherwise
// do not stringify the state if the storage type is async
(this.asyncStorage
? merge({}, state || {}, this.mergeOption)
: (
this.supportCircular
? FlattedJSON.stringify(state) as any
: JSON.stringify(state) as any
)
)
)
)
)
/**
* Async version of plugin
* @param {Store<S>} store
*/
this.plugin = (store: Store<S>) => {
/**
* For async stores, we're capturing the Promise returned
* by the `restoreState()` function in a `restored` property
* on the store itself. This would allow app developers to
* determine when and if the store's state has indeed been
* refreshed. This approach was suggested by GitHub user @hotdogee.
* See https://github.com/championswimmer/vuex-persist/pull/118#issuecomment-500914963
* @since 2.1.0
*/
(store as any).restored = ((this.restoreState(this.key, this.storage)) as Promise<S>).then((savedState) => {
/**
* If in strict mode, do only via mutation
*/
if (this.strictMode) {
store.commit('RESTORE_MUTATION', savedState)
} else {
store.replaceState(merge(store.state, savedState || {}, this.mergeOption) as S)
}
this.subscriber(store)((mutation: MutationPayload, state: S) => {
if (this.filter(mutation)) {
this._mutex.enqueue(
this.saveState(this.key, this.reducer(state), this.storage) as Promise<void>
)
}
})
this.subscribed = true
})
}
} else {
/**
* Sync {@link #VuexPersistence.restoreState} implementation
* @type {((key: string, storage?: Storage) =>
* (Promise<S> | S)) | ((key: string, storage: Storage) => (any | string | {}))}
*/
this.restoreState = (
(options.restoreState != null)
? options.restoreState
: ((key: string, storage: Storage) => {
const value = (storage).getItem(key)
if (typeof value === 'string') {// If string, parse, or else, just return
return (
this.supportCircular
? FlattedJSON.parse(value || '{}')
: JSON.parse(value || '{}')
)
} else {
return (value || {})
}
})
)
/**
* Sync {@link #VuexPersistence.saveState} implementation
* @type {((key: string, state: {}, storage?: Storage) =>
* (Promise<void> | void)) | ((key: string, state: {}, storage?: Storage) => Promise<void>)}
*/
this.saveState = (
(options.saveState != null)
? options.saveState
: ((key: string, state: {}, storage: Storage) =>
(storage).setItem(
key, // Second argument is state _object_ if localforage, stringified otherwise
(
this.supportCircular
? FlattedJSON.stringify(state) as any
: JSON.stringify(state) as any
)
)
)
)
/**
* Sync version of plugin
* @param {Store<S>} store
*/
this.plugin = (store: Store<S>) => {
const savedState = this.restoreState(this.key, this.storage) as S
if (this.strictMode) {
store.commit('RESTORE_MUTATION', savedState)
} else {
store.replaceState(merge(store.state, savedState || {}, this.mergeOption) as S)
}
this.subscriber(store)((mutation: MutationPayload, state: S) => {
if (this.filter(mutation)) {
this.saveState(this.key, this.reducer(state), this.storage)
}
})
this.subscribed = true
}
}
}
/**
* Creates a subscriber on the store. automatically is used
* when this is used a vuex plugin. Not for manual usage.
* @param store
*/
private subscriber = (store: Store<S>) =>
(handler: (mutation: MutationPayload, state: S) => any) => store.subscribe(handler)
}
export {
MockStorage, AsyncStorage, PersistOptions
}
export default VuexPersistence