z-multi-cache
Version:
this is a caching tool that uses localStorage, sessionStorage, memory storage in combination to provide more convenient get data from the cache
369 lines (352 loc) • 12.7 kB
JavaScript
import storage, {
getItemStoragesMap,
setItemStoragesMap,
setItemStoragesListStr,
getItemStoragesListStr,
originStorageMap
} from "@storages/index";
import { updateUrlSearchPart, getUrlParam } from "./utils";
const NAMESPACE_PREFIX = "$zMultiCachePrefix$";
const DEFAULT_PAGE = "$page$";
const DEFAULT_STORAGE_TYPE = "sessionStorage";
const noop = function() {};
/**
* get type of the parameter.
*
* @param {*} obj
* @returns type
*/
function getType(obj) {
return Object.prototype.toString
.call(obj)
.slice(8, -1)
.toLocaleLowerCase();
}
/**
* judge if the input is a simple object.
*
* @param {*} obj
* @returns
*/
function isSimpleObject(obj) {
return getType(obj) !== "object" ? false : true;
}
function strictCheck(strict, template, page, itemKey) {
if (!strict) {
return;
}
if (isGlobalStore(page)) {
const { globalKeys } = template;
if (getType(globalKeys) !== "array") {
throw new Error("globalKeys need to be an array.");
}
if (
!globalKeys.some(item => {
return item === itemKey;
})
) {
throw new Error(
`key "${itemKey}" should be defined in globalKeys firstly.`
);
}
} else {
const { pages } = template;
if (getType(pages) !== "array") {
throw new Error("pages need to be an array.");
}
if (
!pages.some(item => {
return item === page;
})
) {
throw new Error(
`the page part of scope "${page}" should be defined in pages firstly.`
);
}
}
}
function checkParams({ type }, method = "setItem") {
if (method !== "setItem" && method !== "getItem") {
throw new Error("method need to be one of setItem, getItem");
}
if (method === "setItem") {
if (!setItemStoragesMap[type]) {
throw new Error(
`[setItem]: type should be one of: ${setItemStoragesListStr}, your value is: ${type}`
);
}
} else {
if (!getItemStoragesMap[type]) {
throw new Error(
`[getItem]: type should be one of: ${getItemStoragesListStr}, your value is: ${type}`
);
}
}
}
function getDefaultValue(defaultVal) {
if (typeof defaultVal === "function") {
return defaultVal();
}
return defaultVal;
}
function isGlobalStore(page) {
return page === "global";
}
/**
* parse page and itemKey from scope and key.
*
* @param {*} scope
* @param {*} key
* @param {*} separator
* @returns
*/
function getPageAndItemKey(scope, key, separator) {
if (typeof scope !== "string") {
throw new Error("scope need to be a string.");
}
// scope = scope.replace(/^(\s*)\/?(\s*)|(\s*)\/?(\s*)$/g, '');
scope = scope.replace(/\s*\/?\s*$/g, "");
const idx = scope.indexOf(separator);
const hasSeparator = idx !== -1;
const page = scope.slice(0, hasSeparator ? idx : void 0).trim();
let keyPrefix = scope
.substr(hasSeparator ? idx + separator.length : scope.length)
.trim();
keyPrefix = keyPrefix.length ? `${keyPrefix}>` : `${keyPrefix}`;
const itemKey = isGlobalStore(page) ? `${key}` : `${keyPrefix}${key}`;
return {
page: page || void 0, // 如果page为''则返回void 0,是为了方便后面处理默认值
itemKey
};
}
/**
* provide the ability to create a store with some config.
*
* @api factory
* @export
* @param {*} [config={
* ns: '$ns$', // namespace, default "$ns$".you can set a ns in a new project, then the data stored in sessionStorage or localStorage will not cover the data from other projects.
* scopeSeparator: '/', // separator in scope string. when you do: store.set({scope:'homePage/flightPart', key:'city', value: 'beijing'}); the key use "/" to distinguish each part.
* strict: true, // it represents whether to use strict mode.
* template: { // if you use strict mode. when you set or get Data. store will validate data according to the template config.
* globalKeys: [], // if you do: store.set({scope: 'global', key: 'city', value: 'beijing'}), if the scope is global, the key will be checked whther it is defined in globalKeys.
* pages: [] // if scope is not global and in strict mode. the scope will be checked whther it is defined in pages. for example. store.set({scope: 'home/flight'}); the first part of
* // the scope is "home", the "home" will be checked whther it is in pages. the rest is not checked.
* }
* }]
* @returns
*/
export function factory(config = {}) {
const {
ns = "$ns$",
strict = false,
template = {},
scopeSeparator = "/",
getterStrict = true
} = config;
if (strict && !isSimpleObject(template)) {
throw new Error(
'when "strict" is true, template is needed. template is a simple object.'
);
}
const partialStoreKey = `${NAMESPACE_PREFIX}-${ns}`;
const store = {
/**
* set data in storage. the storage can be localStorage, sessionStorage, memoryStorage.
*
* @param {*} [opts={
* type: store.types.localStorage, // which type storage to store data. one of: store.types.localStorage, store.types.sessionStorage, store.types.memoryStorage
* scope: 'homePage/flightPart', // if scope is "global", the key will be checked if it is defined in globalKeys. if it is normal scope. for example: 'home/flight', the first part "home"
* // will be check if it is defined in pages while you set strict to be true.
* key: 'city', // store key
* value: 'beijing', // store value
* updateUrlSearchKey
* }]
*/
setItem(key = "", value, opts = {}) {
const {
type = DEFAULT_STORAGE_TYPE,
scope = "global",
errCallBack = noop,
updateUrlSearchKey,
updateUrlSearchKeyTime
} = opts;
checkParams({ type });
const { page = DEFAULT_PAGE, itemKey } = getPageAndItemKey(
scope,
key,
scopeSeparator
);
strictCheck(strict, template, page, itemKey);
const storeKey = `${partialStoreKey}-${page}`;
storage.setItem(type, storeKey, itemKey, value, errCallBack);
// update the search part of url.
updateUrlSearchKey &&
updateUrlSearchPart(
{ [updateUrlSearchKey]: value },
updateUrlSearchKeyTime,
""
);
},
/**
* get data from storage
*
* @param {*} [opts={
* type: store.types.sessionStorage,
* scope: 'global',
* key: 'city'
* }]
* @returns
*/
getItem(key = "", opts = {}) {
let {
type = DEFAULT_STORAGE_TYPE,
scope = "global",
updateUrlSearchKey,
updateUrlSearchKeyTime
} = opts;
let defaultValue = opts.default;
defaultValue = getDefaultValue(defaultValue);
// 当type是一个数组的时候,按照数组顺序为优先级来取数据
if (getType(type) === "array") {
for (let i = 0, len = type.length; i < len; i++) {
const result = this.getItem(key, {
...opts,
type: type[i],
default: void 0
});
if (result) {
return result;
} else {
continue;
}
}
return defaultValue;
}
if (isSimpleObject(type)) {
key = type.key || key;
type = type.type;
}
checkParams({ type }, "getItem");
let returnValue;
if (type === "urlSearch") {
const val = getUrlParam(key);
returnValue = val || defaultValue;
} else {
const { page = DEFAULT_PAGE, itemKey } = getPageAndItemKey(
scope,
key,
scopeSeparator
);
if (!!getterStrict) {
strictCheck(strict, template, page, itemKey);
}
const storeKey = `${partialStoreKey}-${page}`;
const value = storage.getItem(type, storeKey, itemKey);
returnValue =
value === void 0 || value === null ? defaultValue : value;
}
updateUrlSearchKey &&
updateUrlSearchPart(
{ [updateUrlSearchKey]: returnValue },
updateUrlSearchKeyTime,
""
);
return returnValue;
},
/**
* remove item from storage.
*
* @param {*} key
* @param {*} [opts={}]
*/
removeItem(key, opts = {}) {
const { type = DEFAULT_STORAGE_TYPE, scope = "global" } = opts;
checkParams({ type });
const { page = DEFAULT_PAGE, itemKey } = getPageAndItemKey(
scope,
key,
scopeSeparator
);
strictCheck(strict, template, page, itemKey);
const storeKey = `${partialStoreKey}-${page}`;
storage.removeItem(type, storeKey, itemKey);
},
/**
* clear one page data from storage.
*
* @param {*} [opts={}]
*/
clear(opts = {}) {
const key = "";
const { type = DEFAULT_STORAGE_TYPE, scope = "global" } = opts;
checkParams({ type });
const { page = DEFAULT_PAGE, itemKey } = getPageAndItemKey(
scope,
key,
scopeSeparator
);
strictCheck(strict, template, page, itemKey);
const storeKey = `${partialStoreKey}-${page}`;
storage.clear(type, storeKey);
},
// clear all storage generate from this util.
// type can be 'sessionStorage', 'localStorage', or an array.
clearAll(type = 'sessionStorage') {
if (getType(type) === 'array') {
type.forEach((_type) => {
this.clearAll(_type);
});
return;
}
if (!setItemStoragesMap[type]) {
throw new Error(
`type should be one of: ${setItemStoragesListStr}, your value is: ${type}`
);
}
if (strict) {
const pages = template.pages || [];
const allPages = pages.concat('global');
allPages.forEach((page) => {
const storeKey = `${partialStoreKey}-${page}`;
originStorageMap[type].removeItem(storeKey);
});
} else {
const keys = Object.keys(originStorageMap[type]);
keys.forEach((key) => {
if (key.indexOf(partialStoreKey) > -1) {
originStorageMap[type].removeItem(key);
}
});
}
},
types: getItemStoragesMap,
/**
* update the param in the url search part.
*
* @param {*} [map={}]
*
* map = {
* city: ['hotelCity', { scope: 'global' }], // 'city' is from the search part of url. 'hotelCity' is the key of the date stored in storage.
* name: ['userName', {scope: 'home', type: 'localStorage' }]
* }
*/
updateUrlSearch(map = {}, theTime, title) {
let storeVal = {};
Object.keys(map).forEach(item => {
const storeCfgArr = map[item];
if (getType(storeCfgArr) === 'array') {
storeVal[item] = this.getItem(storeCfgArr[0], storeCfgArr[1]);
} else {
storeVal[item] = storeCfgArr;
}
});
updateUrlSearchPart(storeVal, theTime, title);
},
updateUrlSearchByValue: updateUrlSearchPart,
getUrlParam
};
return store;
}
const store = factory();
export default store;