🐛 fix: slove swr mutate not work in Cache Provider (#10895)

fix: slove swr mutate not work in Cache Provider
This commit is contained in:
Shinji-Li
2025-12-23 13:03:08 +08:00
committed by arvinxx
parent 95c6840162
commit b3fbffe428
24 changed files with 110 additions and 43 deletions
+23 -5
View File
@@ -1,12 +1,28 @@
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React, { PropsWithChildren, useState } from 'react';
import { SWRConfig } from 'swr';
import React, { PropsWithChildren, useEffect, useState } from 'react';
import { SWRConfig, useSWRConfig } from 'swr';
import { setScopedMutate } from '@/libs/swr';
import { swrCacheProvider } from '@/libs/swr/localStorageProvider';
import { lambdaQuery, lambdaQueryClient } from '@/libs/trpc/client';
/**
* Initialize scoped mutate for use outside React components (e.g., Zustand stores)
* This component must be rendered inside SWRConfig to access the scoped mutate
*/
const SWRMutateInitializer = ({ children }: PropsWithChildren) => {
const { mutate } = useSWRConfig();
useEffect(() => {
setScopedMutate(mutate);
}, [mutate]);
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>;
};
const QueryProvider = ({ children }: PropsWithChildren) => {
const [queryClient] = useState(() => new QueryClient());
// Cast required because pnpm installs separate QueryClient type instances for trpc and app
@@ -19,9 +35,11 @@ const QueryProvider = ({ children }: PropsWithChildren) => {
return (
<SWRConfig value={{ provider }}>
<lambdaQuery.Provider client={lambdaQueryClient} queryClient={providerQueryClient}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</lambdaQuery.Provider>
<SWRMutateInitializer>
<lambdaQuery.Provider client={lambdaQueryClient} queryClient={providerQueryClient}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</lambdaQuery.Provider>
</SWRMutateInitializer>
</SWRConfig>
);
};
+3
View File
@@ -96,3 +96,6 @@ export type SWRefreshMethod<T> = <A extends (...args: any[]) => Promise<any>>(
// 导出带自动同步功能的 hook
export { useClientDataSWRWithSync } from './useClientDataSWRWithSync';
// 导出 scoped mutate(用于自定义 cache provider 场景)
export { mutate, setScopedMutate } from './mutate';
+45
View File
@@ -0,0 +1,45 @@
/**
* Scoped SWR Mutate
*
* When using a custom cache provider with SWRConfig, the global `mutate` from 'swr'
* becomes a no-op because it can't access the scoped cache.
*
* This module provides a scoped mutate function that works with custom cache providers.
* The mutate function is initialized when the SWRConfig component mounts.
*
* @see https://github.com/vercel/swr/issues/2799
*
* @example
* ```ts
* // Instead of:
* import { mutate } from 'swr';
*
* // Use:
* import { mutate } from '@/libs/swr';
* ```
*/
import type { ScopedMutator } from 'swr/_internal';
// Global scoped mutate reference, set when SWRConfig mounts
let scopedMutate: ScopedMutator | null = null;
/**
* Set the scoped mutate function from SWRConfig
* Called internally by SWRProvider on mount
*/
export const setScopedMutate = (m: ScopedMutator) => {
scopedMutate = m;
};
/**
* Scoped mutate function that works with custom cache providers
*
* Use this instead of `import { mutate } from 'swr'` when using localStorage cache provider
*/
export const mutate: ScopedMutator = ((key: any, data?: any, opts?: any) => {
if (!scopedMutate) {
console.warn('[SWR] Scoped mutate not initialized, this may cause cache sync issues');
return Promise.resolve([]);
}
return scopedMutate(key, data, opts);
}) as ScopedMutator;
+2 -2
View File
@@ -1,12 +1,12 @@
import { getSingletonAnalyticsOptional } from '@lobehub/analytics';
import isEqual from 'fast-deep-equal';
import { produce } from 'immer';
import { SWRResponse, mutate } from 'swr';
import type { SWRResponse } from 'swr';
import type { PartialDeep } from 'type-fest';
import { StateCreator } from 'zustand/vanilla';
import { MESSAGE_CANCEL_FLAT } from '@/const/message';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { CreateAgentParams, CreateAgentResult, agentService } from '@/services/agent';
import { getUserStoreState } from '@/store/user';
import { userProfileSelectors } from '@/store/user/selectors';
+2 -2
View File
@@ -1,8 +1,8 @@
import { KnowledgeItem } from '@lobechat/types';
import { SWRResponse, mutate } from 'swr';
import type { SWRResponse } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { agentService } from '@/services/agent';
import type { AgentStore } from '../../store';
+1 -2
View File
@@ -1,11 +1,10 @@
import type { AgentGroupDetail } from '@lobechat/types';
import isEqual from 'fast-deep-equal';
import { produce } from 'immer';
import { mutate } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import type { ChatGroupItem } from '@/database/schemas/chatGroup';
import { useClientDataSWRWithSync } from '@/libs/swr';
import { mutate, useClientDataSWRWithSync } from '@/libs/swr';
import { chatGroupService } from '@/services/chatGroup';
import { getAgentStoreState } from '@/store/agent';
import { ChatGroupStore } from '@/store/agentGroup/store';
+2 -2
View File
@@ -5,10 +5,10 @@ import {
CreateAiModelParams,
ToggleAiModelEnableParams,
} from 'model-bank';
import { SWRResponse, mutate } from 'swr';
import type { SWRResponse } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { aiModelService } from '@/services/aiModel';
import { AiInfraStore } from '@/store/aiInfra/store';
@@ -9,10 +9,10 @@ import {
ModelParamsSchema,
Pricing,
} from 'model-bank';
import { SWRResponse, mutate } from 'swr';
import type { SWRResponse } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { aiProviderService } from '@/services/aiProvider';
import { AiInfraStore } from '@/store/aiInfra/store';
import { useUserStore } from '@/store/user';
@@ -1,9 +1,9 @@
import { parse } from '@lobechat/conversation-flow';
import { ConversationContext, UIChatMessage } from '@lobechat/types';
import isEqual from 'fast-deep-equal';
import { mutate } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { mutate } from '@/libs/swr';
import { ChatStore } from '@/store/chat/store';
import { MessageMapKeyInput, messageMapKey } from '../../../utils/messageMapKey';
+2 -2
View File
@@ -4,10 +4,10 @@ import { LOADING_FLAT } from '@lobechat/const';
import { chainSummaryTitle } from '@lobechat/prompts';
import { CreateMessageParams, IThreadType, ThreadItem, UIChatMessage } from '@lobechat/types';
import isEqual from 'fast-deep-equal';
import { SWRResponse, mutate } from 'swr';
import type { SWRResponse } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { chatService } from '@/services/chat';
import { threadService } from '@/services/thread';
import { threadSelectors } from '@/store/chat/selectors';
+2 -2
View File
@@ -5,12 +5,12 @@ import { chainSummaryTitle } from '@lobechat/prompts';
import { TraceNameMap, UIChatMessage } from '@lobechat/types';
import isEqual from 'fast-deep-equal';
import { t } from 'i18next';
import useSWR, { SWRResponse, mutate } from 'swr';
import useSWR, { SWRResponse } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { message } from '@/components/AntdStaticMethods';
import { LOADING_FLAT } from '@/const/message';
import { useClientDataSWRWithSync } from '@/libs/swr';
import { mutate, useClientDataSWRWithSync } from '@/libs/swr';
import { chatService } from '@/services/chat';
import { messageService } from '@/services/message';
import { topicService } from '@/services/topic';
+2 -1
View File
@@ -1,8 +1,9 @@
import { NetworkProxySettings, ShortcutUpdateResult } from '@lobechat/electron-client-ipc';
import isEqual from 'fast-deep-equal';
import useSWR, { SWRResponse, mutate } from 'swr';
import useSWR, { SWRResponse } from 'swr';
import type { StateCreator } from 'zustand/vanilla';
import { mutate } from '@/libs/swr';
import { desktopSettingsService } from '@/services/electron/settings';
import type { ElectronStore } from '../store';
+2 -1
View File
@@ -1,8 +1,9 @@
import { DataSyncConfig } from '@lobechat/electron-client-ipc';
import isEqual from 'fast-deep-equal';
import useSWR, { SWRResponse, mutate } from 'swr';
import useSWR, { SWRResponse } from 'swr';
import type { StateCreator } from 'zustand/vanilla';
import { mutate } from '@/libs/swr';
import { remoteServerService } from '@/services/electron/remoteServer';
import { initialState } from '../initialState';
+2 -2
View File
@@ -1,10 +1,10 @@
import { buildFolderTree, sanitizeFolderName, topologicalSortFolders } from '@lobechat/utils';
import pMap from 'p-map';
import { SWRResponse, mutate } from 'swr';
import type { SWRResponse } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { FILE_UPLOAD_BLACKLIST, MAX_UPLOAD_FILE_COUNT } from '@/const/file';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { FileService, fileService } from '@/services/file';
import { ragService } from '@/services/rag';
import {
+1 -2
View File
@@ -1,10 +1,9 @@
import isEqual from 'fast-deep-equal';
import type { SWRResponse } from 'swr';
import { mutate } from 'swr';
import type { StateCreator } from 'zustand/vanilla';
import type { SidebarAgentItem, SidebarAgentListResponse } from '@/database/repositories/home';
import { useClientDataSWR, useClientDataSWRWithSync } from '@/libs/swr';
import { mutate, useClientDataSWR, useClientDataSWRWithSync } from '@/libs/swr';
import { homeService } from '@/services/home';
import type { HomeStore } from '@/store/home/store';
import { setNamespace } from '@/utils/storeDebug';
@@ -1,9 +1,9 @@
import { isEqual } from 'es-toolkit/compat';
import { useRef } from 'react';
import { SWRResponse, mutate } from 'swr';
import type { SWRResponse } from 'swr';
import { StateCreator } from 'zustand';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { GetGenerationStatusResult } from '@/server/routers/lambda/generation';
import { generationService } from '@/services/generation';
import { generationBatchService } from '@/services/generationBatch';
@@ -1,10 +1,10 @@
import { chainSummaryGenerationTitle } from '@lobechat/prompts';
import isEqual from 'fast-deep-equal';
import { SWRResponse, mutate } from 'swr';
import type { SWRResponse } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { LOADING_FLAT } from '@/const/message';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { UpdateTopicValue } from '@/server/routers/lambda/generationTopic';
import { chatService } from '@/services/chat';
import { generationTopicService } from '@/services/generationTopic';
@@ -1,7 +1,7 @@
import { SWRResponse, mutate } from 'swr';
import type { SWRResponse } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { knowledgeBaseService } from '@/services/knowledgeBase';
import { KnowledgeBaseStore } from '@/store/knowledgeBase/store';
import { CreateKnowledgeBaseParams, KnowledgeBaseItem } from '@/types/knowledgeBase';
@@ -4,11 +4,11 @@ import {
RAGEvalDataSetItem,
insertEvalDatasetRecordSchema,
} from '@lobechat/types';
import { SWRResponse, mutate } from 'swr';
import type { SWRResponse } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { notification } from '@/components/AntdStaticMethods';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { ragEvalService } from '@/services/ragEval';
import { KnowledgeBaseStore } from '@/store/knowledgeBase/store';
@@ -1,8 +1,8 @@
import { CreateNewEvalEvaluation, RAGEvalDataSetItem } from '@lobechat/types';
import { SWRResponse, mutate } from 'swr';
import type { SWRResponse } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { ragEvalService } from '@/services/ragEval';
import { KnowledgeBaseStore } from '@/store/knowledgeBase/store';
+2 -2
View File
@@ -1,14 +1,14 @@
import { getSingletonAnalyticsOptional } from '@lobehub/analytics';
import isEqual from 'fast-deep-equal';
import { t } from 'i18next';
import useSWR, { SWRResponse, mutate } from 'swr';
import useSWR, { SWRResponse } from 'swr';
import type { PartialDeep } from 'type-fest';
import { StateCreator } from 'zustand/vanilla';
import { message } from '@/components/AntdStaticMethods';
import { DEFAULT_AGENT_LOBE_SESSION, INBOX_SESSION_ID } from '@/const/session';
import { DEFAULT_CHAT_GROUP_CHAT_CONFIG } from '@/const/settings';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { chatGroupService } from '@/services/chatGroup';
import { sessionService } from '@/services/session';
import { getChatGroupStoreState } from '@/store/agentGroup';
+2 -1
View File
@@ -2,10 +2,11 @@ import { LobeTool } from '@lobechat/types';
import { uniqBy } from 'es-toolkit/compat';
import { t } from 'i18next';
import { produce } from 'immer';
import useSWR, { SWRResponse, mutate } from 'swr';
import useSWR, { SWRResponse } from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { notification } from '@/components/AntdStaticMethods';
import { mutate } from '@/libs/swr';
import { pluginService } from '@/services/plugin';
import { toolService } from '@/services/tool';
import { globalHelpers } from '@/store/global/helpers';
+2 -2
View File
@@ -1,11 +1,11 @@
import { isDesktop } from '@lobechat/const';
import { getSingletonAnalyticsOptional } from '@lobehub/analytics';
import useSWR, { SWRResponse, mutate } from 'swr';
import useSWR, { SWRResponse } from 'swr';
import type { PartialDeep } from 'type-fest';
import type { StateCreator } from 'zustand/vanilla';
import { DEFAULT_PREFERENCE } from '@/const/user';
import { useOnlyFetchOnceSWR } from '@/libs/swr';
import { mutate, useOnlyFetchOnceSWR } from '@/libs/swr';
import { userService } from '@/services/user';
import type { UserStore } from '@/store/user';
import type { GlobalServerConfig } from '@/types/serverConfig';
+2 -2
View File
@@ -1,9 +1,9 @@
import isEqual from 'fast-deep-equal';
import { type SWRResponse, mutate } from 'swr';
import type { SWRResponse } from 'swr';
import useSWR from 'swr';
import { StateCreator } from 'zustand/vanilla';
import { useClientDataSWR } from '@/libs/swr';
import { mutate, useClientDataSWR } from '@/libs/swr';
import { userMemoryService } from '@/services/userMemory';
import { LayersEnum } from '@/types/userMemory';
import type { RetrieveMemoryParams, RetrieveMemoryResult } from '@/types/userMemory';