UNPKG

storetify

Version:

Enhanced localStorage with expiration, subscription, and full TypeScript support

337 lines (331 loc) 12.3 kB
(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; }));