storetify
Version:
Enhanced localStorage with expiration, subscription, and full TypeScript support
337 lines (331 loc) • 12.3 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, (function () {
var current = global.Storetify;
var exports = global.Storetify = factory();
exports.noConflict = function () { global.Storetify = current; return exports; };
})());
})(this, (function () { 'use strict';
const BIND_FLAG = Symbol.for("__storetify_bind_window_event_flag__");
function getGlobal() {
if (typeof globalThis !== "undefined")
return globalThis;
if (typeof window !== "undefined")
return window;
// eslint-disable-next-line no-restricted-globals
if (typeof self !== "undefined")
return self;
if (typeof global !== "undefined")
return global;
return undefined;
}
function hasBindWindowEventStorage() {
return !!getGlobal()[BIND_FLAG];
}
function setBindWindowEventStorage(flag) {
getGlobal()[BIND_FLAG] = flag;
}
function dispatchStorageEvent({ key, newValue, oldValue, type, }) {
var _a, _b, _c;
const naturalStorageEvent = new StorageEvent(type, {
key,
newValue,
oldValue,
url: (_b = (_a = getGlobal()) === null || _a === void 0 ? void 0 : _a.location) === null || _b === void 0 ? void 0 : _b.href,
storageArea: localStorage,
});
(_c = getGlobal()) === null || _c === void 0 ? void 0 : _c.dispatchEvent(naturalStorageEvent);
}
function jsonParse(data) {
if (data === null || data === "undefined" || data === undefined) {
return null;
}
try {
return JSON.parse(data);
}
catch (_a) {
return null;
}
}
function each(funcs, ev, defaultKey) {
var _a, _b, _c, _d;
let newValue = null;
let oldValue = null;
try {
newValue = (_b = (_a = jsonParse(ev.newValue)) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : newValue;
oldValue = (_d = (_c = jsonParse(ev.oldValue)) === null || _c === void 0 ? void 0 : _c.value) !== null && _d !== void 0 ? _d : oldValue;
}
catch (error) {
console.warn(`has an exception in your json parse, from ${ev.url}, .eg ${error}`);
}
const mutationalStorageEvent = {
type: ev.type,
key: ev.key === null ? defaultKey : ev.key,
newValue,
oldValue,
url: ev.url,
isTrusted: ev.isTrusted,
native: ev,
};
funcs.forEach((func) => {
if (typeof func === "function") {
try {
func(mutationalStorageEvent);
}
catch (error) {
console.warn(`has an exception in your ${func.name || "anonymous function"}( ev ){
${error}
}`);
}
}
});
}
function isValidKey(key) {
if (typeof key !== "string") {
console.warn("store failed, entry a valid string key.");
}
}
/**
* NextStorage
* Next localStorage
*/
class NextStorage {
// eslint-disable-next-line no-useless-constructor
constructor(store) {
this.store = store;
this.namespace = "storetify";
this.observers = new Map();
this.windowEventStorage = false;
}
// SP
static getInstance(store) {
var _a;
if (store === void 0) { store = (_a = getGlobal()) === null || _a === void 0 ? void 0 : _a.localStorage; }
if (!NextStorage.storage) {
NextStorage.storage = new NextStorage(store);
}
return NextStorage.storage;
}
setNamespace(space) {
this.namespace = space;
}
getNamespace() {
return this.namespace;
}
getUsed() {
let totalSize = 0;
const store = this.getStore();
for (let i = 0; i < store.length; i++) {
const key = store.key(i);
if (key) {
const value = store.getItem(key);
totalSize += key.length + ((value === null || value === void 0 ? void 0 : value.length) || 0);
}
}
const storageUsed = totalSize / 1024;
return `${storageUsed.toFixed(3)} KB`;
}
setStore(store) {
this.store = store;
}
getStore() {
return this.store;
}
set(key, value, expires) {
isValidKey(key);
const val = JSON.stringify({ value, expires: expires ? expires * 1000 + Date.now() : expires });
try {
const oldValue = this.getItem(key);
dispatchStorageEvent({ key, newValue: val, oldValue, type: "storage" });
}
finally {
this.getStore().setItem(key, val);
}
return this;
}
get(key) {
var _a;
const val = (_a = this.getStore().getItem(key)) !== null && _a !== void 0 ? _a : null;
if (val === null) {
return val;
}
const parsed = jsonParse(val);
if (parsed && (!parsed.expires || Date.now() <= parsed.expires)) {
return parsed.value;
}
// only del store key without listeners
this.remove(key, true);
return null;
}
getItem(key) {
var _a;
const val = (_a = this.getStore().getItem(key)) !== null && _a !== void 0 ? _a : null;
if (val === null) {
return val;
}
const parsed = jsonParse(val);
if (parsed && (!parsed.expires || Date.now() <= parsed.expires)) {
return val;
}
// this.remove(key) only read value
return null;
}
has(key) {
const val = this.getStore().getItem(key);
if (val === null)
return false;
return true;
}
publish(observers, e, force = false, defaultKey = null) {
// force publish without ask observers
if (!force && typeof observers === "string" && !this.has(observers))
return;
if (Array.isArray(observers)) {
each(observers, e, defaultKey);
}
else {
const storeListeners = this.getObserver(observers);
each(storeListeners, e, defaultKey);
}
}
publishAll(e) {
this.observers.forEach((item, key) => {
this.publish(item, e, true, key);
});
}
subscribe(key, listener) {
const { observers } = this;
const listeners = observers.get(key);
if (listeners) {
listeners.push(listener);
}
else {
this.observers.set(key, [listener]);
}
return this;
}
getObserver(key) {
var _a;
return (_a = this.observers.get(key)) !== null && _a !== void 0 ? _a : [];
}
unsubscribe(keys, listener) {
if (keys === undefined || keys === null) {
// 没有 keys,清空所有 observers
this.observers.clear();
}
else if (Array.isArray(keys)) {
// keys 是数组,删除数组中的每个 key(空数组不会删除任何内容)
keys.forEach(key => {
this.observers.delete(key);
});
}
else if (typeof keys === "string" && listener) {
// keys 是字符串且有 listener,删除该 key 对应的特定 listener
const listeners = this.observers.get(keys);
if (listeners) {
const filtered = listeners.filter((item) => {
// 通过引用匹配删除
if (item === listener)
return false;
// 如果引用不同,但有显式设置的名称(非空且非默认值),则通过名称匹配
if (listener.name && listener.name !== "mockConstructor" && item.name === listener.name) {
return false;
}
return true;
});
if (filtered.length > 0) {
this.observers.set(keys, filtered);
}
else {
this.observers.delete(keys);
}
}
}
else if (typeof keys === "string") {
// keys 是字符串但没有 listener,删除该 key 的所有 listeners
this.observers.delete(keys);
}
}
remove(key, soft) {
var _a;
const oldValue = (_a = this.getStore().getItem(key)) !== null && _a !== void 0 ? _a : null;
dispatchStorageEvent({ key, newValue: null, oldValue, type: "storage" });
if (soft !== true) {
this.unsubscribe(key);
}
this.getStore().removeItem(key);
return this;
}
clear() {
dispatchStorageEvent({ key: null, newValue: null, oldValue: null, type: "storage" });
// go dispatchPublish storage.unsubscribe()
// this.unsubscribe()
this.getStore().clear();
}
}
// eslint-disable-next-line no-use-before-define
NextStorage.storage = null;
const storage = NextStorage.getInstance();
function store(...rest) {
const len = rest.length;
const [key, value, expires] = rest;
if (len === 1 && typeof key === "string") {
return storage.get(key);
}
if (len >= 2 && typeof key === "string") {
if (value === undefined) {
return storage.remove(key);
}
if (typeof value === "function") {
return storage.set(key, value(), expires);
}
return storage.set(key, value, expires);
}
return null;
}
function init() {
const skipNames = ["constructor", "getStore"];
const propertyNames = Object.getOwnPropertyNames(Object.getPrototypeOf(storage));
propertyNames.forEach(key => {
if (skipNames.includes(key))
return;
const value = storage[key];
const storeKey = key;
if (typeof value === "function") {
store[storeKey] = value.bind(storage);
}
});
Object.defineProperty(store, "storage", {
value: storage,
writable: false,
configurable: false,
enumerable: false,
});
function dispatchPublish(ev) {
const { key, newValue, oldValue, isTrusted } = ev;
if (key !== null && newValue !== oldValue) {
storage.publish(key, ev, true);
return;
}
if (key === null && newValue === null && oldValue === null) {
storage.publishAll(ev);
// Consistent behavior with storage.clear(), while unsubscribe.
if (isTrusted !== true) {
storage.unsubscribe();
}
}
}
// SP
if (!hasBindWindowEventStorage()) {
setBindWindowEventStorage(true);
const handleStorageChange = (e) => {
dispatchPublish(e);
};
window.addEventListener("storage", handleStorageChange);
}
return store;
}
var store$1 = init();
return store$1;
}));