index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
  4. var deepmerge = _interopDefault(require('deepmerge'));
  5. /**
  6. * Created by championswimmer on 22/07/17.
  7. */
  8. // @ts-ignore
  9. {
  10. exports.MockStorage = class {
  11. get length() {
  12. return Object.keys(this).length;
  13. }
  14. key(index) {
  15. return Object.keys(this)[index];
  16. }
  17. setItem(key, data) {
  18. this[key] = data.toString();
  19. }
  20. getItem(key) {
  21. return this[key];
  22. }
  23. removeItem(key) {
  24. delete this[key];
  25. }
  26. clear() {
  27. for (let key of Object.keys(this)) {
  28. delete this[key];
  29. }
  30. }
  31. };
  32. }
  33. // tslint:disable: variable-name
  34. class SimplePromiseQueue {
  35. constructor() {
  36. this._queue = [];
  37. this._flushing = false;
  38. }
  39. enqueue(promise) {
  40. this._queue.push(promise);
  41. if (!this._flushing) {
  42. return this.flushQueue();
  43. }
  44. return Promise.resolve();
  45. }
  46. flushQueue() {
  47. this._flushing = true;
  48. const chain = () => {
  49. const nextTask = this._queue.shift();
  50. if (nextTask) {
  51. return nextTask.then(chain);
  52. }
  53. else {
  54. this._flushing = false;
  55. }
  56. };
  57. return Promise.resolve(chain());
  58. }
  59. }
  60. const options = {
  61. replaceArrays: {
  62. arrayMerge: (destinationArray, sourceArray, options) => sourceArray
  63. },
  64. concatArrays: {
  65. arrayMerge: (target, source, options) => target.concat(...source)
  66. }
  67. };
  68. function merge(into, from, mergeOption) {
  69. return deepmerge(into, from, options[mergeOption]);
  70. }
  71. let FlattedJSON = JSON;
  72. /**
  73. * A class that implements the vuex persistence.
  74. * @type S type of the 'state' inside the store (default: any)
  75. */
  76. class VuexPersistence {
  77. /**
  78. * Create a {@link VuexPersistence} object.
  79. * Use the <code>plugin</code> function of this class as a
  80. * Vuex plugin.
  81. * @param {PersistOptions} options
  82. */
  83. constructor(options) {
  84. // tslint:disable-next-line:variable-name
  85. this._mutex = new SimplePromiseQueue();
  86. /**
  87. * Creates a subscriber on the store. automatically is used
  88. * when this is used a vuex plugin. Not for manual usage.
  89. * @param store
  90. */
  91. this.subscriber = (store) => (handler) => store.subscribe(handler);
  92. if (typeof options === 'undefined')
  93. options = {};
  94. this.key = ((options.key != null) ? options.key : 'vuex');
  95. this.subscribed = false;
  96. this.supportCircular = options.supportCircular || false;
  97. if (this.supportCircular) {
  98. FlattedJSON = require('flatted');
  99. }
  100. this.mergeOption = options.mergeOption || 'replaceArrays';
  101. let localStorageLitmus = true;
  102. try {
  103. window.localStorage.getItem('');
  104. }
  105. catch (err) {
  106. localStorageLitmus = false;
  107. }
  108. /**
  109. * 1. First, prefer storage sent in optinos
  110. * 2. Otherwise, use window.localStorage if available
  111. * 3. Finally, try to use MockStorage
  112. * 4. None of above? Well we gotta fail.
  113. */
  114. if (options.storage) {
  115. this.storage = options.storage;
  116. }
  117. else if (localStorageLitmus) {
  118. this.storage = window.localStorage;
  119. }
  120. else if (exports.MockStorage) {
  121. this.storage = new exports.MockStorage();
  122. }
  123. else {
  124. throw new Error("Neither 'window' is defined, nor 'MockStorage' is available");
  125. }
  126. /**
  127. * How this works is -
  128. * 1. If there is options.reducer function, we use that, if not;
  129. * 2. We check options.modules;
  130. * 1. If there is no options.modules array, we use entire state in reducer
  131. * 2. Otherwise, we create a reducer that merges all those state modules that are
  132. * defined in the options.modules[] array
  133. * @type {((state: S) => {}) | ((state: S) => S) | ((state: any) => {})}
  134. */
  135. this.reducer = ((options.reducer != null)
  136. ? options.reducer
  137. : ((options.modules == null)
  138. ? ((state) => state)
  139. : ((state) => options.modules.reduce((a, i) => merge(a, { [i]: state[i] }, this.mergeOption), { /* start empty accumulator*/}))));
  140. this.filter = options.filter || ((mutation) => true);
  141. this.strictMode = options.strictMode || false;
  142. this.RESTORE_MUTATION = function RESTORE_MUTATION(state, savedState) {
  143. const mergedState = merge(state, savedState || {}, this.mergeOption);
  144. for (const propertyName of Object.keys(mergedState)) {
  145. this._vm.$set(state, propertyName, mergedState[propertyName]);
  146. }
  147. };
  148. this.asyncStorage = options.asyncStorage || false;
  149. if (this.asyncStorage) {
  150. /**
  151. * Async {@link #VuexPersistence.restoreState} implementation
  152. * @type {((key: string, storage?: Storage) =>
  153. * (Promise<S> | S)) | ((key: string, storage: AsyncStorage) => Promise<any>)}
  154. */
  155. this.restoreState = ((options.restoreState != null)
  156. ? options.restoreState
  157. : ((key, storage) => (storage).getItem(key)
  158. .then((value) => typeof value === 'string' // If string, parse, or else, just return
  159. ? (this.supportCircular
  160. ? FlattedJSON.parse(value || '{}')
  161. : JSON.parse(value || '{}'))
  162. : (value || {}))));
  163. /**
  164. * Async {@link #VuexPersistence.saveState} implementation
  165. * @type {((key: string, state: {}, storage?: Storage) =>
  166. * (Promise<void> | void)) | ((key: string, state: {}, storage?: Storage) => Promise<void>)}
  167. */
  168. this.saveState = ((options.saveState != null)
  169. ? options.saveState
  170. : ((key, state, storage) => (storage).setItem(key, // Second argument is state _object_ if asyc storage, stringified otherwise
  171. // do not stringify the state if the storage type is async
  172. (this.asyncStorage
  173. ? merge({}, state || {}, this.mergeOption)
  174. : (this.supportCircular
  175. ? FlattedJSON.stringify(state)
  176. : JSON.stringify(state))))));
  177. /**
  178. * Async version of plugin
  179. * @param {Store<S>} store
  180. */
  181. this.plugin = (store) => {
  182. /**
  183. * For async stores, we're capturing the Promise returned
  184. * by the `restoreState()` function in a `restored` property
  185. * on the store itself. This would allow app developers to
  186. * determine when and if the store's state has indeed been
  187. * refreshed. This approach was suggested by GitHub user @hotdogee.
  188. * See https://github.com/championswimmer/vuex-persist/pull/118#issuecomment-500914963
  189. * @since 2.1.0
  190. */
  191. store.restored = (this.restoreState(this.key, this.storage)).then((savedState) => {
  192. /**
  193. * If in strict mode, do only via mutation
  194. */
  195. if (this.strictMode) {
  196. store.commit('RESTORE_MUTATION', savedState);
  197. }
  198. else {
  199. store.replaceState(merge(store.state, savedState || {}, this.mergeOption));
  200. }
  201. this.subscriber(store)((mutation, state) => {
  202. if (this.filter(mutation)) {
  203. this._mutex.enqueue(this.saveState(this.key, this.reducer(state), this.storage));
  204. }
  205. });
  206. this.subscribed = true;
  207. });
  208. };
  209. }
  210. else {
  211. /**
  212. * Sync {@link #VuexPersistence.restoreState} implementation
  213. * @type {((key: string, storage?: Storage) =>
  214. * (Promise<S> | S)) | ((key: string, storage: Storage) => (any | string | {}))}
  215. */
  216. this.restoreState = ((options.restoreState != null)
  217. ? options.restoreState
  218. : ((key, storage) => {
  219. const value = (storage).getItem(key);
  220. if (typeof value === 'string') { // If string, parse, or else, just return
  221. return (this.supportCircular
  222. ? FlattedJSON.parse(value || '{}')
  223. : JSON.parse(value || '{}'));
  224. }
  225. else {
  226. return (value || {});
  227. }
  228. }));
  229. /**
  230. * Sync {@link #VuexPersistence.saveState} implementation
  231. * @type {((key: string, state: {}, storage?: Storage) =>
  232. * (Promise<void> | void)) | ((key: string, state: {}, storage?: Storage) => Promise<void>)}
  233. */
  234. this.saveState = ((options.saveState != null)
  235. ? options.saveState
  236. : ((key, state, storage) => (storage).setItem(key, // Second argument is state _object_ if localforage, stringified otherwise
  237. (this.supportCircular
  238. ? FlattedJSON.stringify(state)
  239. : JSON.stringify(state)))));
  240. /**
  241. * Sync version of plugin
  242. * @param {Store<S>} store
  243. */
  244. this.plugin = (store) => {
  245. const savedState = this.restoreState(this.key, this.storage);
  246. if (this.strictMode) {
  247. store.commit('RESTORE_MUTATION', savedState);
  248. }
  249. else {
  250. store.replaceState(merge(store.state, savedState || {}, this.mergeOption));
  251. }
  252. this.subscriber(store)((mutation, state) => {
  253. if (this.filter(mutation)) {
  254. this.saveState(this.key, this.reducer(state), this.storage);
  255. }
  256. });
  257. this.subscribed = true;
  258. };
  259. }
  260. }
  261. }
  262. exports.VuexPersistence = VuexPersistence;
  263. exports.default = VuexPersistence;
  264. //# sourceMappingURL=index.js.map