index.js 9.4 KB

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