Skip to content

Commit

Permalink
Add subclass to VuexPersistence to handle modules in a memory-safe way
Browse files Browse the repository at this point in the history
  • Loading branch information
robbevp committed Jan 21, 2021
1 parent f97750a commit 85c59da
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 21 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"core-js": "^3.8.2",
"fetch-retry": "^4.0.1",
"localforage": "^1.9.0",
"lodash.debounce": "^4.0.8",
"roboto-fontface": "^0.10.0",
"vue": "^2.6.12",
"vue-headful": "^2.1.0",
Expand Down
4 changes: 2 additions & 2 deletions src/store/albums.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export default {
async index({ commit, rootState }) {
const generator = index(rootState.auth);
try {
await store.restored;
await store.albumsRestored;
await fetchAll(commit, generator, "setAlbums");
return true;
} catch (error) {
Expand All @@ -129,7 +129,7 @@ export default {
async read({ commit, rootState }, id) {
try {
const result = await read(rootState.auth, id);
await store.restored;
await store.albumsRestored;
commit("setAlbum", { id, album: result });
return result.id;
} catch (error) {
Expand Down
4 changes: 2 additions & 2 deletions src/store/artists.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default {
async index({ commit, rootState }) {
const generator = index(rootState.auth);
try {
await store.restored;
await store.artistsRestored;
await fetchAll(commit, generator, "setArtists");
return true;
} catch (error) {
Expand All @@ -79,7 +79,7 @@ export default {
async read({ commit, rootState }, id) {
try {
const result = await read(rootState.auth, id);
await store.restored;
await store.artistsRestored;
commit("setArtist", { id, artist: result });
return result.id;
} catch (error) {
Expand Down
4 changes: 2 additions & 2 deletions src/store/genres.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default {
async index({ commit, rootState }) {
const generator = index(rootState.auth);
try {
await store.restored;
await store.genresRestored;
await fetchAll(commit, generator, "setGenres");
return true;
} catch (error) {
Expand All @@ -79,7 +79,7 @@ export default {
async read({ commit, rootState }, id) {
try {
const result = await read(rootState.auth, id);
await store.restored;
await store.genresRestored;
commit("setGenre", { id, genre: result });
return result.id;
} catch (error) {
Expand Down
4 changes: 2 additions & 2 deletions src/store/labels.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default {
async index({ commit, rootState }) {
const generator = index(rootState.auth);
try {
await store.restored;
await store.labelsRestored;
await fetchAll(commit, generator, "setLabels");
return true;
} catch (error) {
Expand All @@ -79,7 +79,7 @@ export default {
async read({ commit, rootState }, id) {
try {
const result = await read(rootState.auth, id);
await store.restored;
await store.labelsRestored;
commit("setLabel", { id, label: result });
return result.id;
} catch (error) {
Expand Down
84 changes: 74 additions & 10 deletions src/store/persistence.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,75 @@
import VuexPersistence from "vuex-persist";
import localForage from "localforage";
import debounce from "lodash.debounce";

// A subclass for VuexPersintence to deal with single namepaced modules and large collections
// Should be provided with a module and an async storage
// IMPORTANT: This class assumes a lot about the structure of module and the available mutations
// If this structure changes, we inevitably have to update this class
class VuexPersistentModule extends VuexPersistence {
constructor(module, storage) {
// Write startLoading and the main collection (as an array) to storage
async function saveModule(module, state, storage) {
const modifiedState = {
startLoading: state[module].startLoading,
};
modifiedState[module] = Object.values(state[module][module]);
await storage.setItem(module, modifiedState);
}

// Set all options in parent class
super({
storage,
asyncStorage: true,
modules: [module],
key: module,
filter: (m) => m.type.substring(0, m.type.indexOf("/")) === module,
saveState: debounce(saveModule, 60000),
});

// Get item from an async storage and commit them back to the store
this.replaceState = async (module, storage, store) => {
const savedState = await storage.getItem(module);
if (savedState) {
if (savedState.startLoading) {
store.commit(`${module}/setStartLoading`, savedState.startLoading);
}
if (savedState[module]) {
store.commit(
`${module}/set${module.charAt(0).toUpperCase() + module.slice(1)}`,
savedState[module]
);
}
}
};

// Overwrite plugin to use replaceState and set a custom restored prop for each module
this.plugin = (store) => {
store[`${module}Restored`] = this.replaceState(
this.key,
this.storage,
store
).then(() => {
// The subscriber is the same as in VuexPersistence, but we have to add it ourselves since we overwrite the plugin method
this.subscriber(store)((mutation, state) => {
if (this.filter(mutation)) {
this._mutex.enqueue(
this.saveState(this.key, this.reducer(state), this.storage)
);
}
});
this.subscribed = true;
});
};
}
}

const plugins = ["albums", "artists", "genres", "labels", "tracks"].map(
(module) => {
const plugin = new VuexPersistentModule(module, localForage);
return plugin.plugin;
}
);

const localStorageModules = [
"auth",
Expand All @@ -12,16 +82,6 @@ const localStorageModules = [
"users",
"userSettings",
];
const localForageModules = ["albums", "artists", "genres", "labels"];

export const vuexLocalForage = new VuexPersistence({
storage: localForage,
asyncStorage: true,
strictMode: process.env.NODE_ENV !== "production",
modules: localForageModules,
filter: (m) =>
localForageModules.includes(m.type.substring(0, m.type.indexOf("/"))),
});

export const vuexLocalStorage = new VuexPersistence({
storage: window.localStorage,
Expand All @@ -30,3 +90,7 @@ export const vuexLocalStorage = new VuexPersistence({
filter: (m) =>
localStorageModules.includes(m.type.substring(0, m.type.indexOf("/"))),
});

plugins.push(vuexLocalStorage.plugin);

export default plugins;
6 changes: 3 additions & 3 deletions src/store/store.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Vue from "vue";
import Vuex from "vuex";
import { vuexLocalForage, vuexLocalStorage } from "./persistence";
import persistencePlugins, { vuexLocalStorage } from "./persistence";
import albums from "./albums";
import artists from "./artists";
import auth from "./auth";
Expand Down Expand Up @@ -35,11 +35,11 @@ const mutations = {
};

if (process.env.NODE_ENV !== "production") {
mutations.RESTORE_MUTATION = vuexLocalForage.RESTORE_MUTATION;
mutations.RESTORE_MUTATION = vuexLocalStorage.RESTORE_MUTATION;
}

export default new Vuex.Store({
plugins: [vuexLocalForage.plugin, vuexLocalStorage.plugin],
plugins: persistencePlugins,
strict: process.env.NODE_ENV !== "production",
modules: {
albums,
Expand Down
3 changes: 3 additions & 0 deletions src/store/tracks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Vue from "vue";
import { index, create, destroy, update, read, merge } from "../api/tracks";
import { fetchAll } from "./actions";
import { compareTracks } from "../comparators";
import store from "./store";

export default {
namespaced: true,
Expand Down Expand Up @@ -100,6 +101,7 @@ export default {
async index({ commit, rootState }) {
const generator = index(rootState.auth);
try {
await store.tracksRestored;
await fetchAll(commit, generator, "setTracks");
return true;
} catch (error) {
Expand All @@ -120,6 +122,7 @@ export default {
async read({ commit, rootState }, id) {
try {
const track = await read(rootState.auth, id);
await store.tracksRestored;
commit("setTrack", { id, track });
return true;
} catch (error) {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5698,6 +5698,11 @@ locate-path@^5.0.0:
dependencies:
p-locate "^4.1.0"

lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=

lodash.defaultsdeep@^4.6.1:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6"
Expand Down

0 comments on commit 85c59da

Please sign in to comment.