@lobehub/chat
Version:
Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.
127 lines (105 loc) • 4.26 kB
text/typescript
import { PersistStorage, StorageValue } from 'zustand/middleware';
import { createIndexedDB } from './indexedDB';
import { createKeyMapper } from './keyMapper';
import { createLocalStorage } from './localStorage';
import { HyperStorageOptions } from './type';
import { creatUrlStorage } from './urlStorage';
export const createHyperStorage = <T extends object>(
options: HyperStorageOptions,
): PersistStorage<T> => {
const optionsObj = typeof options === 'function' ? options() : options;
const getLocalStorageConfig = () => {
if (optionsObj.localStorage === false) {
return { dbName: '', skipLocalStorage: true, useIndexedDB: false };
}
const useIndexedDB = optionsObj.localStorage?.mode === 'indexedDB';
const dbName = optionsObj.localStorage?.dbName || 'indexedDB';
return { dbName, skipLocalStorage: false, useIndexedDB };
};
const hasUrl = !!optionsObj.url;
const { skipLocalStorage, useIndexedDB, dbName } = getLocalStorageConfig();
const { mapStateKeyToStorageKey, getStateKeyFromStorageKey } = createKeyMapper(optionsObj);
const indexedDB = createIndexedDB(dbName);
const localStorage = createLocalStorage();
const urlStorage = creatUrlStorage(optionsObj.url?.mode);
return {
getItem: async (name): Promise<StorageValue<T>> => {
const state: any = {};
let version: number | undefined;
// ============== 处理 Local Storage ============== //
if (!skipLocalStorage) {
let localState: StorageValue<T> | undefined;
// 如果使用 indexedDB,优先从 indexedDB 中获取
if (useIndexedDB) {
localState = await indexedDB.getItem(name);
}
// 如果 indexedDB 中不存在,则再试试 localStorage
if (!localState) localState = localStorage.getItem(name);
if (localState) {
version = localState.version;
for (const [k, v] of Object.entries(localState.state)) {
const key = getStateKeyFromStorageKey(k, 'localStorage');
if (key) state[key] = v;
}
}
}
// ============== 处理 URL Storage ============== //
// 不从 URL 中获取 version,由于 url 状态是临时态 不作为版本控制的依据
if (hasUrl) {
const urlState = urlStorage.getItem();
if (urlState) {
for (const [k, v] of Object.entries(urlState.state)) {
const key = getStateKeyFromStorageKey(k, 'url');
// 当存在 UrlSelector 逻辑,且 key 有效时,才将状态加入终态 state
if (hasUrl && key) {
state[key] = v;
}
}
}
}
return { state, version };
},
removeItem: async (key) => {
// ============== 处理 Local Storage ============== //
if (!skipLocalStorage) {
if (useIndexedDB) {
await indexedDB.removeItem(key);
} else {
localStorage.removeItem(key);
}
}
// ============== 处理 URL Storage ============== //
if (hasUrl) {
const storageKey = getStateKeyFromStorageKey(key, 'url');
urlStorage.removeItem(storageKey);
}
},
setItem: async (name, newValue) => {
// ============== 处理 Local Storage ============== //
const localState: Record<string, any> = {};
for (const [k, v] of Object.entries(newValue.state)) {
const localKey = mapStateKeyToStorageKey(k, 'localStorage');
if (localKey) localState[localKey] = v;
}
if (!skipLocalStorage) {
if (useIndexedDB) {
await indexedDB.setItem(name, localState, newValue.version);
} else {
localStorage.setItem(name, localState, newValue.version);
}
}
// ============== 处理 URL Storage ============== //
if (hasUrl) {
// 转换 key 为目标 key
const finalEntries = Object.entries(newValue.state)
.map(([k, v]) => {
const urlKey = mapStateKeyToStorageKey(k, 'url');
if (!urlKey) return undefined;
return [urlKey, v];
})
.filter(Boolean) as [string, any][];
urlStorage.setItem(name, Object.fromEntries(finalEntries));
}
},
};
};