mobx-persist-store
Version:
Mobx Persist Store
170 lines (169 loc) • 8.18 kB
JavaScript
import { action, isAction, isComputedProp, makeObservable, observable, ObservableMap, ObservableSet, reaction, runInAction, toJS, } from 'mobx';
import { PersistStoreMap } from './PersistStoreMap';
import { StorageAdapter } from './StorageAdapter';
import { mpsConfig, mpsReactionOptions } from './configurePersistable';
import { makeSerializableProperties } from './serializableProperty';
import { actionPersistWarningIf, computedPersistWarningIf, consoleDebug, invalidStorageAdaptorWarningIf, isArrayForMap, isArrayForSet, } from './utils';
export class PersistStore {
constructor(target, options, reactionOptions = {}) {
var _a, _b, _c, _d, _e, _f, _g, _h;
this.cancelWatch = null;
this.properties = [];
this.reactionOptions = {};
this.storageAdapter = null;
this.target = null;
this.version = undefined;
this.debugMode = false;
this.isHydrated = false;
this.isPersisting = false;
this.storageName = '';
this.target = target;
this.storageName = options.name;
this.version = (_a = options.version) !== null && _a !== void 0 ? _a : mpsConfig.version;
this.properties = makeSerializableProperties(options.properties);
this.reactionOptions = Object.assign({ fireImmediately: true }, mpsReactionOptions, reactionOptions);
this.debugMode = (_c = (_b = options.debugMode) !== null && _b !== void 0 ? _b : mpsConfig.debugMode) !== null && _c !== void 0 ? _c : false;
this.storageAdapter = new StorageAdapter({
version: this.version,
expireIn: (_d = options.expireIn) !== null && _d !== void 0 ? _d : mpsConfig.expireIn,
removeOnExpiration: (_f = (_e = options.removeOnExpiration) !== null && _e !== void 0 ? _e : mpsConfig.removeOnExpiration) !== null && _f !== void 0 ? _f : true,
stringify: (_h = (_g = options.stringify) !== null && _g !== void 0 ? _g : mpsConfig.stringify) !== null && _h !== void 0 ? _h : true,
storage: options.storage ? options.storage : mpsConfig.storage,
debugMode: this.debugMode,
});
makeObservable(this, {
clearPersistedStore: action,
hydrateStore: action,
isHydrated: observable,
isPersisting: observable,
pausePersisting: action,
startPersisting: action,
stopPersisting: action,
}, { autoBind: true, deep: false });
invalidStorageAdaptorWarningIf(this.storageAdapter.options.storage, this.storageName);
consoleDebug(this.debugMode, `${this.storageName} - (makePersistable)`, {
properties: this.properties,
storageAdapter: this.storageAdapter,
reactionOptions: this.reactionOptions,
});
}
async init() {
await this.hydrateStore();
this.startPersisting();
return this;
}
async hydrateStore() {
// If the user calls stopPersist and then rehydrateStore we don't want to automatically call startPersist below
const isBeingWatched = Boolean(this.cancelWatch);
if (this.isPersisting) {
this.pausePersisting();
}
runInAction(() => {
this.isHydrated = false;
consoleDebug(this.debugMode, `${this.storageName} - (hydrateStore) isHydrated:`, this.isHydrated);
});
if (this.storageAdapter && this.target) {
const data = await this.storageAdapter.getItem(this.storageName);
// Reassigning so TypeScript doesn't complain (Object is possibly 'null') about this.target within forEach
const target = this.target;
if (data) {
runInAction(() => {
this.properties.forEach((property) => {
const allowPropertyHydration = [
property.key in target,
typeof data[property.key] !== 'undefined',
].every(Boolean);
if (allowPropertyHydration) {
const propertyData = data[property.key];
if (target[property.key] instanceof ObservableMap && isArrayForMap(propertyData)) {
target[property.key] = property.deserialize(new Map(propertyData));
}
else if (target[property.key] instanceof ObservableSet && isArrayForSet(propertyData)) {
target[property.key] = property.deserialize(new Set(propertyData));
}
else {
target[property.key] = property.deserialize(propertyData);
}
}
});
});
}
}
runInAction(() => {
this.isHydrated = true;
consoleDebug(this.debugMode, `${this.storageName} - isHydrated:`, this.isHydrated);
});
if (isBeingWatched) {
this.startPersisting();
}
}
startPersisting() {
if (!this.storageAdapter || !this.target || this.cancelWatch) {
return;
}
// Reassigning so TypeScript doesn't complain (Object is possibly 'null') about and this.target within reaction
const target = this.target;
this.cancelWatch = reaction(() => {
const propertiesToWatch = {};
this.properties.forEach((property) => {
const isComputedProperty = isComputedProp(target, property.key);
const isActionProperty = isAction(target[property.key]);
computedPersistWarningIf(isComputedProperty, String(property.key));
actionPersistWarningIf(isActionProperty, String(property.key));
if (!isComputedProperty && !isActionProperty) {
let propertyData = property.serialize(target[property.key]);
if (propertyData instanceof ObservableMap) {
const mapArray = [];
propertyData.forEach((v, k) => {
mapArray.push([k, toJS(v)]);
});
propertyData = mapArray;
}
else if (propertyData instanceof ObservableSet) {
propertyData = Array.from(propertyData).map(toJS);
}
propertiesToWatch[property.key] = toJS(propertyData);
}
});
return propertiesToWatch;
}, async (dataToSave) => {
if (this.storageAdapter) {
await this.storageAdapter.setItem(this.storageName, dataToSave);
}
}, this.reactionOptions);
this.isPersisting = true;
consoleDebug(this.debugMode, `${this.storageName} - (startPersisting) isPersisting:`, this.isPersisting);
}
pausePersisting() {
this.isPersisting = false;
consoleDebug(this.debugMode, `${this.storageName} - pausePersisting (isPersisting):`, this.isPersisting);
if (this.cancelWatch) {
this.cancelWatch();
this.cancelWatch = null;
}
}
stopPersisting() {
this.pausePersisting();
consoleDebug(this.debugMode, `${this.storageName} - (stopPersisting)`);
PersistStoreMap.delete(this.target);
this.cancelWatch = null;
this.properties = [];
this.reactionOptions = {};
this.storageAdapter = null;
this.target = null;
}
async clearPersistedStore() {
if (this.storageAdapter) {
consoleDebug(this.debugMode, `${this.storageName} - (clearPersistedStore)`);
await this.storageAdapter.removeItem(this.storageName);
}
}
async getPersistedStore() {
if (this.storageAdapter) {
consoleDebug(this.debugMode, `${this.storageName} - (getPersistedStore)`);
// @ts-ignore
return this.storageAdapter.getItem(this.storageName);
}
return null;
}
}