From 501b7c1b4aec6cc4424c8b98a61cf8965acc5b59 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 3 Apr 2026 17:33:33 -0700 Subject: [PATCH] improvement(stores): remove deployment state from Zustand in favor of React Query --- .../copilot/checkpoints/revert/route.test.ts | 13 --- .../api/copilot/checkpoints/revert/route.ts | 1 - .../deployments/[version]/revert/route.ts | 1 - apps/sim/app/api/workflows/[id]/route.ts | 2 - .../templates/components/template-card.tsx | 2 - .../[workspaceId]/home/hooks/use-chat.ts | 11 -- .../templates/components/template-card.tsx | 2 - .../components/deploy-modal/deploy-modal.tsx | 8 +- .../panel/components/deploy/deploy.tsx | 10 +- .../hooks/use-current-workflow.ts | 7 -- .../w/[workflowId]/utils/auto-layout-utils.ts | 3 +- .../workspace/providers/socket-provider.tsx | 1 - apps/sim/hooks/queries/deployments.ts | 60 +++------- .../queries/utils/custom-tool-cache.test.ts | 39 ------- .../hooks/queries/utils/custom-tool-cache.ts | 23 ---- apps/sim/hooks/use-collaborative-workflow.ts | 1 - .../tools/client/tool-display-registry.ts | 15 ++- apps/sim/lib/logs/types.ts | 3 +- apps/sim/lib/workflows/defaults.ts | 2 - .../lib/workflows/persistence/utils.test.ts | 1 - apps/sim/socket/database/operations.ts | 1 - apps/sim/stores/index.ts | 1 - apps/sim/stores/workflows/index.ts | 105 +---------------- apps/sim/stores/workflows/registry/store.ts | 110 +++--------------- apps/sim/stores/workflows/registry/types.ts | 16 --- apps/sim/stores/workflows/workflow/store.ts | 74 ------------ apps/sim/stores/workflows/workflow/types.ts | 5 - apps/sim/tools/index.test.ts | 17 ++- 28 files changed, 64 insertions(+), 470 deletions(-) delete mode 100644 apps/sim/hooks/queries/utils/custom-tool-cache.test.ts delete mode 100644 apps/sim/hooks/queries/utils/custom-tool-cache.ts diff --git a/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts b/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts index fe3246181d4..7fd68b4925e 100644 --- a/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts +++ b/apps/sim/app/api/copilot/checkpoints/revert/route.test.ts @@ -304,7 +304,6 @@ describe('Copilot Checkpoints Revert API Route', () => { loops: {}, parallels: {}, isDeployed: true, - deploymentStatuses: { production: 'deployed' }, }, } @@ -349,7 +348,6 @@ describe('Copilot Checkpoints Revert API Route', () => { loops: {}, parallels: {}, isDeployed: true, - deploymentStatuses: { production: 'deployed' }, lastSaved: 1640995200000, }, }, @@ -370,7 +368,6 @@ describe('Copilot Checkpoints Revert API Route', () => { loops: {}, parallels: {}, isDeployed: true, - deploymentStatuses: { production: 'deployed' }, lastSaved: 1640995200000, }), } @@ -473,7 +470,6 @@ describe('Copilot Checkpoints Revert API Route', () => { edges: undefined, loops: null, parallels: undefined, - deploymentStatuses: null, }, } @@ -508,7 +504,6 @@ describe('Copilot Checkpoints Revert API Route', () => { loops: {}, parallels: {}, isDeployed: false, - deploymentStatuses: {}, lastSaved: 1640995200000, }) }) @@ -768,10 +763,6 @@ describe('Copilot Checkpoints Revert API Route', () => { parallel1: { branches: ['branch1', 'branch2'] }, }, isDeployed: true, - deploymentStatuses: { - production: 'deployed', - staging: 'pending', - }, deployedAt: '2024-01-01T10:00:00.000Z', }, } @@ -816,10 +807,6 @@ describe('Copilot Checkpoints Revert API Route', () => { parallel1: { branches: ['branch1', 'branch2'] }, }, isDeployed: true, - deploymentStatuses: { - production: 'deployed', - staging: 'pending', - }, deployedAt: '2024-01-01T10:00:00.000Z', lastSaved: 1640995200000, }) diff --git a/apps/sim/app/api/copilot/checkpoints/revert/route.ts b/apps/sim/app/api/copilot/checkpoints/revert/route.ts index 2edf7d2dec7..dd73477f5ec 100644 --- a/apps/sim/app/api/copilot/checkpoints/revert/route.ts +++ b/apps/sim/app/api/copilot/checkpoints/revert/route.ts @@ -82,7 +82,6 @@ export async function POST(request: NextRequest) { loops: checkpointState?.loops || {}, parallels: checkpointState?.parallels || {}, isDeployed: checkpointState?.isDeployed || false, - deploymentStatuses: checkpointState?.deploymentStatuses || {}, lastSaved: Date.now(), ...(checkpointState?.deployedAt && checkpointState.deployedAt !== null && diff --git a/apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.ts b/apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.ts index 618a1de8f94..a209db29eb4 100644 --- a/apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.ts +++ b/apps/sim/app/api/workflows/[id]/deployments/[version]/revert/route.ts @@ -79,7 +79,6 @@ export async function POST( loops: deployedState.loops || {}, parallels: deployedState.parallels || {}, lastSaved: Date.now(), - deploymentStatuses: deployedState.deploymentStatuses || {}, }) if (!saveResult.success) { diff --git a/apps/sim/app/api/workflows/[id]/route.ts b/apps/sim/app/api/workflows/[id]/route.ts index 95a6ee43dc7..3d74fe527fa 100644 --- a/apps/sim/app/api/workflows/[id]/route.ts +++ b/apps/sim/app/api/workflows/[id]/route.ts @@ -89,7 +89,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ const finalWorkflowData = { ...workflowData, state: { - deploymentStatuses: {}, blocks: normalizedData.blocks, edges: normalizedData.edges, loops: normalizedData.loops, @@ -115,7 +114,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ const emptyWorkflowData = { ...workflowData, state: { - deploymentStatuses: {}, blocks: {}, edges: [], loops: {}, diff --git a/apps/sim/app/templates/components/template-card.tsx b/apps/sim/app/templates/components/template-card.tsx index 15fb0cec4cc..7a2f3d2e105 100644 --- a/apps/sim/app/templates/components/template-card.tsx +++ b/apps/sim/app/templates/components/template-card.tsx @@ -108,8 +108,6 @@ function normalizeWorkflowState(input?: any): WorkflowState | null { lastUpdate: input.lastUpdate, metadata: input.metadata, variables: input.variables, - deploymentStatuses: input.deploymentStatuses, - needsRedeployment: input.needsRedeployment, dragStartPosition: input.dragStartPosition ?? null, } diff --git a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts index 5fa965d9abe..88866a77000 100644 --- a/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts +++ b/apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts @@ -1407,17 +1407,6 @@ export function useChat( const output = tc.result?.output as Record | undefined const deployedWorkflowId = (output?.workflowId as string) ?? undefined if (deployedWorkflowId && typeof output?.isDeployed === 'boolean') { - const isDeployed = output.isDeployed as boolean - const serverDeployedAt = output.deployedAt - ? new Date(output.deployedAt as string) - : undefined - useWorkflowRegistry - .getState() - .setDeploymentStatus( - deployedWorkflowId, - isDeployed, - isDeployed ? (serverDeployedAt ?? new Date()) : undefined - ) queryClient.invalidateQueries({ queryKey: deploymentKeys.info(deployedWorkflowId), }) diff --git a/apps/sim/app/workspace/[workspaceId]/templates/components/template-card.tsx b/apps/sim/app/workspace/[workspaceId]/templates/components/template-card.tsx index 8671a709271..a93cb915c69 100644 --- a/apps/sim/app/workspace/[workspaceId]/templates/components/template-card.tsx +++ b/apps/sim/app/workspace/[workspaceId]/templates/components/template-card.tsx @@ -111,8 +111,6 @@ function normalizeWorkflowState(input?: any): WorkflowState | null { lastUpdate: input.lastUpdate, metadata: input.metadata, variables: input.variables, - deploymentStatuses: input.deploymentStatuses, - needsRedeployment: input.needsRedeployment, dragStartPosition: input.dragStartPosition ?? null, } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx index f6ce4e95c2b..4133728b666 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx @@ -40,7 +40,6 @@ import { useWorkflowMap } from '@/hooks/queries/workflows' import { useWorkspaceSettings } from '@/hooks/queries/workspace' import { usePermissionConfig } from '@/hooks/use-permission-config' import { useSettingsNavigation } from '@/hooks/use-settings-navigation' -import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { mergeSubblockState } from '@/stores/workflows/utils' import { useWorkflowStore } from '@/stores/workflows/workflow/store' import type { WorkflowState } from '@/stores/workflows/workflow/types' @@ -90,10 +89,7 @@ export function DeployModal({ const params = useParams() const workspaceId = params?.workspaceId as string const { navigateToSettings } = useSettingsNavigation() - const deploymentStatus = useWorkflowRegistry((state) => - state.getWorkflowDeploymentStatus(workflowId) - ) - const isDeployed = deploymentStatus?.isDeployed ?? isDeployedProp + const isDeployed = isDeployedProp const { data: workflowMap = {} } = useWorkflowMap(workspaceId) const workflowMetadata = workflowId ? workflowMap[workflowId] : undefined const workflowWorkspaceId = workflowMetadata?.workspaceId ?? null @@ -381,8 +377,6 @@ export function DeployModal({ invalidateDeploymentQueries(queryClient, workflowId) - useWorkflowRegistry.getState().setWorkflowNeedsRedeployment(workflowId, false) - if (chatSuccessTimeoutRef.current) { clearTimeout(chatSuccessTimeoutRef.current) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/deploy.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/deploy.tsx index 78081e6eeac..24a0975325c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/deploy.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/deploy.tsx @@ -9,7 +9,7 @@ import { useDeployment, } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/hooks' import { useCurrentWorkflow } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow' -import { useDeployedWorkflowState } from '@/hooks/queries/deployments' +import { useDeployedWorkflowState, useDeploymentInfo } from '@/hooks/queries/deployments' import type { WorkspaceUserPermissions } from '@/hooks/use-user-permissions' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' @@ -25,10 +25,10 @@ export function Deploy({ activeWorkflowId, userPermissions, className }: DeployP const isRegistryLoading = hydrationPhase === 'idle' || hydrationPhase === 'state-loading' const { hasBlocks } = useCurrentWorkflow() - const deploymentStatus = useWorkflowRegistry((state) => - state.getWorkflowDeploymentStatus(activeWorkflowId) - ) - const isDeployed = deploymentStatus?.isDeployed || false + const { data: deploymentInfo } = useDeploymentInfo(activeWorkflowId, { + enabled: !isRegistryLoading, + }) + const isDeployed = deploymentInfo?.isDeployed ?? false const isDeployedStateEnabled = Boolean(activeWorkflowId) && isDeployed && !isRegistryLoading const { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow.ts index a74573b07b0..85143170e5c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-current-workflow.ts @@ -2,7 +2,6 @@ import { useMemo } from 'react' import type { Edge } from 'reactflow' import { useShallow } from 'zustand/react/shallow' import { useWorkflowDiffStore } from '@/stores/workflow-diff/store' -import type { DeploymentStatus } from '@/stores/workflows/registry/types' import { useWorkflowStore } from '@/stores/workflows/workflow/store' import type { BlockState, Loop, Parallel, WorkflowState } from '@/stores/workflows/workflow/types' @@ -16,8 +15,6 @@ export interface CurrentWorkflow { loops: Record parallels: Record lastSaved?: number - deploymentStatuses?: Record - needsRedeployment?: boolean // Mode information isDiffMode: boolean @@ -48,8 +45,6 @@ export function useCurrentWorkflow(): CurrentWorkflow { loops: state.loops, parallels: state.parallels, lastSaved: state.lastSaved, - deploymentStatuses: state.deploymentStatuses, - needsRedeployment: state.needsRedeployment, })) ) @@ -78,8 +73,6 @@ export function useCurrentWorkflow(): CurrentWorkflow { loops: activeWorkflow.loops || {}, parallels: activeWorkflow.parallels || {}, lastSaved: activeWorkflow.lastSaved, - deploymentStatuses: activeWorkflow.deploymentStatuses, - needsRedeployment: activeWorkflow.needsRedeployment, // Mode information - update to reflect ready state isDiffMode: hasActiveDiff && isShowingDiff, diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts index 3b4d5a73ee5..69a15ec7d67 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/auto-layout-utils.ts @@ -124,8 +124,7 @@ export async function applyAutoLayoutAndUpdateStore( try { useWorkflowStore.getState().updateLastSaved() - const { deploymentStatuses, needsRedeployment, dragStartPosition, ...stateToSave } = - newWorkflowState + const { dragStartPosition, ...stateToSave } = newWorkflowState const cleanedWorkflowState = { ...stateToSave, diff --git a/apps/sim/app/workspace/providers/socket-provider.tsx b/apps/sim/app/workspace/providers/socket-provider.tsx index 6a27bd3a664..47c00157d0f 100644 --- a/apps/sim/app/workspace/providers/socket-provider.tsx +++ b/apps/sim/app/workspace/providers/socket-provider.tsx @@ -424,7 +424,6 @@ export function SocketProvider({ children, user }: SocketProviderProps) { loops: workflowState.loops || {}, parallels: workflowState.parallels || {}, lastSaved: workflowState.lastSaved || Date.now(), - deploymentStatuses: workflowState.deploymentStatuses || {}, }) useSubBlockStore.setState((state: any) => ({ diff --git a/apps/sim/hooks/queries/deployments.ts b/apps/sim/hooks/queries/deployments.ts index 62f5e970d3b..7bcf8be904d 100644 --- a/apps/sim/hooks/queries/deployments.ts +++ b/apps/sim/hooks/queries/deployments.ts @@ -5,7 +5,6 @@ import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tansta import type { WorkflowDeploymentVersionResponse } from '@/lib/workflows/persistence/utils' import { fetchDeploymentVersionState } from '@/hooks/queries/utils/fetch-deployment-version-state' import { workflowKeys } from '@/hooks/queries/utils/workflow-keys' -import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import type { WorkflowState } from '@/stores/workflows/workflow/types' const logger = createLogger('DeploymentQueries') @@ -321,7 +320,6 @@ interface DeployWorkflowResult { */ export function useDeployWorkflow() { const queryClient = useQueryClient() - const setDeploymentStatus = useWorkflowRegistry((state) => state.setDeploymentStatus) return useMutation({ mutationFn: async ({ @@ -351,18 +349,12 @@ export function useDeployWorkflow() { warnings: data.warnings, } }, - onSuccess: (data, variables) => { - logger.info('Workflow deployed successfully', { workflowId: variables.workflowId }) - - setDeploymentStatus( - variables.workflowId, - data.isDeployed, - data.deployedAt ? new Date(data.deployedAt) : undefined, - data.apiKey - ) - - useWorkflowRegistry.getState().setWorkflowNeedsRedeployment(variables.workflowId, false) - + onSettled: (_data, error, variables) => { + if (error) { + logger.error('Failed to deploy workflow', { error }) + } else { + logger.info('Workflow deployed successfully', { workflowId: variables.workflowId }) + } return Promise.all([ invalidateDeploymentQueries(queryClient, variables.workflowId), queryClient.invalidateQueries({ @@ -370,9 +362,6 @@ export function useDeployWorkflow() { }), ]) }, - onError: (error) => { - logger.error('Failed to deploy workflow', { error }) - }, }) } @@ -389,7 +378,6 @@ interface UndeployWorkflowVariables { */ export function useUndeployWorkflow() { const queryClient = useQueryClient() - const setDeploymentStatus = useWorkflowRegistry((state) => state.setDeploymentStatus) return useMutation({ mutationFn: async ({ workflowId }: UndeployWorkflowVariables): Promise => { @@ -402,11 +390,12 @@ export function useUndeployWorkflow() { throw new Error(errorData.error || 'Failed to undeploy workflow') } }, - onSuccess: (_, variables) => { - logger.info('Workflow undeployed successfully', { workflowId: variables.workflowId }) - - setDeploymentStatus(variables.workflowId, false) - + onSettled: (_data, error, variables) => { + if (error) { + logger.error('Failed to undeploy workflow', { error }) + } else { + logger.info('Workflow undeployed successfully', { workflowId: variables.workflowId }) + } return Promise.all([ invalidateDeploymentQueries(queryClient, variables.workflowId), queryClient.invalidateQueries({ @@ -414,9 +403,6 @@ export function useUndeployWorkflow() { }), ]) }, - onError: (error) => { - logger.error('Failed to undeploy workflow', { error }) - }, }) } @@ -613,7 +599,6 @@ interface ActivateVersionResult { */ export function useActivateDeploymentVersion() { const queryClient = useQueryClient() - const setDeploymentStatus = useWorkflowRegistry((state) => state.setDeploymentStatus) return useMutation({ mutationFn: async ({ @@ -663,20 +648,13 @@ export function useActivateDeploymentVersion() { ) } }, - onSuccess: (data, variables) => { - logger.info('Deployment version activated', { - workflowId: variables.workflowId, - version: variables.version, - }) - - setDeploymentStatus( - variables.workflowId, - true, - data.deployedAt ? new Date(data.deployedAt) : undefined, - data.apiKey - ) - }, - onSettled: (_, __, variables) => { + onSettled: (_data, error, variables) => { + if (!error) { + logger.info('Deployment version activated', { + workflowId: variables.workflowId, + version: variables.version, + }) + } return invalidateDeploymentQueries(queryClient, variables.workflowId) }, }) diff --git a/apps/sim/hooks/queries/utils/custom-tool-cache.test.ts b/apps/sim/hooks/queries/utils/custom-tool-cache.test.ts deleted file mode 100644 index c4b7fd54021..00000000000 --- a/apps/sim/hooks/queries/utils/custom-tool-cache.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @vitest-environment node - */ -import { beforeEach, describe, expect, it, vi } from 'vitest' - -const { getQueryDataMock } = vi.hoisted(() => ({ - getQueryDataMock: vi.fn(), -})) - -vi.mock('@/app/_shell/providers/get-query-client', () => ({ - getQueryClient: vi.fn(() => ({ - getQueryData: getQueryDataMock, - })), -})) - -import { getCustomTool, getCustomTools } from '@/hooks/queries/utils/custom-tool-cache' - -describe('custom tool cache helpers', () => { - beforeEach(() => { - vi.clearAllMocks() - }) - - it('reads workspace-scoped custom tools from the cache', () => { - const tools = [{ id: 'tool-1', title: 'Weather', schema: {}, code: '', workspaceId: 'ws-1' }] - getQueryDataMock.mockReturnValue(tools) - - expect(getCustomTools('ws-1')).toBe(tools) - expect(getQueryDataMock).toHaveBeenCalledWith(['customTools', 'list', 'ws-1']) - }) - - it('resolves custom tools by id or title', () => { - getQueryDataMock.mockReturnValue([ - { id: 'tool-1', title: 'Weather', schema: {}, code: '', workspaceId: 'ws-1' }, - ]) - - expect(getCustomTool('tool-1', 'ws-1')?.title).toBe('Weather') - expect(getCustomTool('Weather', 'ws-1')?.id).toBe('tool-1') - }) -}) diff --git a/apps/sim/hooks/queries/utils/custom-tool-cache.ts b/apps/sim/hooks/queries/utils/custom-tool-cache.ts deleted file mode 100644 index 407911ddb37..00000000000 --- a/apps/sim/hooks/queries/utils/custom-tool-cache.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { getQueryClient } from '@/app/_shell/providers/get-query-client' -import type { CustomToolDefinition } from '@/hooks/queries/custom-tools' -import { customToolsKeys } from '@/hooks/queries/utils/custom-tool-keys' - -/** - * Reads custom tools for a workspace directly from the React Query cache. - */ -export function getCustomTools(workspaceId: string): CustomToolDefinition[] { - return ( - getQueryClient().getQueryData(customToolsKeys.list(workspaceId)) ?? [] - ) -} - -/** - * Resolves a custom tool from the cache by id or title. - */ -export function getCustomTool( - identifier: string, - workspaceId: string -): CustomToolDefinition | undefined { - const tools = getCustomTools(workspaceId) - return tools.find((tool) => tool.id === identifier || tool.title === identifier) -} diff --git a/apps/sim/hooks/use-collaborative-workflow.ts b/apps/sim/hooks/use-collaborative-workflow.ts index 4093bed8b20..0b22a47de4b 100644 --- a/apps/sim/hooks/use-collaborative-workflow.ts +++ b/apps/sim/hooks/use-collaborative-workflow.ts @@ -562,7 +562,6 @@ export function useCollaborativeWorkflow() { loops: workflowData.state.loops || {}, parallels: workflowData.state.parallels || {}, lastSaved: workflowData.state.lastSaved || Date.now(), - deploymentStatuses: workflowData.state.deploymentStatuses || {}, }) // Update subblock store with reverted values diff --git a/apps/sim/lib/copilot/tools/client/tool-display-registry.ts b/apps/sim/lib/copilot/tools/client/tool-display-registry.ts index 9a9cb88ca8a..cad83d1ec4c 100644 --- a/apps/sim/lib/copilot/tools/client/tool-display-registry.ts +++ b/apps/sim/lib/copilot/tools/client/tool-display-registry.ts @@ -40,7 +40,11 @@ import { XCircle, Zap, } from 'lucide-react' -import { getCustomTool } from '@/hooks/queries/utils/custom-tool-cache' +import { getQueryClient } from '@/app/_shell/providers/get-query-client' +import type { CustomToolDefinition } from '@/hooks/queries/custom-tools' +import type { WorkflowDeploymentInfo } from '@/hooks/queries/deployments' +import { deploymentKeys } from '@/hooks/queries/deployments' +import { customToolsKeys } from '@/hooks/queries/utils/custom-tool-keys' import { getWorkflowById } from '@/hooks/queries/utils/workflow-cache' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' @@ -445,7 +449,8 @@ const META_deploy_api: ToolMetadata = { const workflowId = params?.workflowId || useWorkflowRegistry.getState().activeWorkflowId const isAlreadyDeployed = workflowId - ? useWorkflowRegistry.getState().getWorkflowDeploymentStatus(workflowId)?.isDeployed + ? (getQueryClient().getQueryData(deploymentKeys.info(workflowId)) + ?.isDeployed ?? false) : false let actionText = action @@ -1053,7 +1058,11 @@ const META_manage_custom_tool: ToolMetadata = { let toolName = params?.schema?.function?.name if (!toolName && params?.toolId && workspaceId) { try { - const tool = getCustomTool(params.toolId, workspaceId) + const tools = + getQueryClient().getQueryData( + customToolsKeys.list(workspaceId) + ) ?? [] + const tool = tools.find((t) => t.id === params.toolId || t.title === params.toolId) toolName = tool?.schema?.function?.name } catch { // Ignore errors accessing cache diff --git a/apps/sim/lib/logs/types.ts b/apps/sim/lib/logs/types.ts index b9402583704..20f568ab41c 100644 --- a/apps/sim/lib/logs/types.ts +++ b/apps/sim/lib/logs/types.ts @@ -2,10 +2,9 @@ import type { Edge } from 'reactflow' import type { AsyncExecutionCorrelation } from '@/lib/core/async-jobs/types' import type { ParentIteration, SerializableExecutionState } from '@/executor/execution/types' import type { BlockLog, NormalizedBlockOutput } from '@/executor/types' -import type { DeploymentStatus } from '@/stores/workflows/registry/types' import type { Loop, Parallel, WorkflowState } from '@/stores/workflows/workflow/types' -export type { WorkflowState, Loop, Parallel, DeploymentStatus } +export type { WorkflowState, Loop, Parallel } export type WorkflowEdge = Edge export type { NormalizedBlockOutput, BlockLog } diff --git a/apps/sim/lib/workflows/defaults.ts b/apps/sim/lib/workflows/defaults.ts index 7de0c058a7d..b0b6f4de2e3 100644 --- a/apps/sim/lib/workflows/defaults.ts +++ b/apps/sim/lib/workflows/defaults.ts @@ -123,8 +123,6 @@ export function buildDefaultWorkflowArtifacts(): DefaultWorkflowArtifacts { loops: {}, parallels: {}, lastSaved: Date.now(), - deploymentStatuses: {}, - needsRedeployment: false, } return { diff --git a/apps/sim/lib/workflows/persistence/utils.test.ts b/apps/sim/lib/workflows/persistence/utils.test.ts index ef4c00fab84..e5e02b74a6d 100644 --- a/apps/sim/lib/workflows/persistence/utils.test.ts +++ b/apps/sim/lib/workflows/persistence/utils.test.ts @@ -985,7 +985,6 @@ describe('Database Helpers', () => { edges: loadedState!.edges, loops: {}, parallels: {}, - deploymentStatuses: {}, } const mockTransaction = vi.fn().mockImplementation(async (callback) => { diff --git a/apps/sim/socket/database/operations.ts b/apps/sim/socket/database/operations.ts index 60cfa177e6b..3e8eeeb99bb 100644 --- a/apps/sim/socket/database/operations.ts +++ b/apps/sim/socket/database/operations.ts @@ -186,7 +186,6 @@ export async function getWorkflowState(workflowId: string) { if (normalizedData) { const finalState = { - deploymentStatuses: {}, hasActiveWebhook: false, blocks: normalizedData.blocks, edges: normalizedData.edges, diff --git a/apps/sim/stores/index.ts b/apps/sim/stores/index.ts index d1bbbc9b227..fc4d647dbbe 100644 --- a/apps/sim/stores/index.ts +++ b/apps/sim/stores/index.ts @@ -203,7 +203,6 @@ export const resetAllStores = () => { useWorkflowRegistry.setState({ activeWorkflowId: null, error: null, - deploymentStatuses: {}, hydration: { phase: 'idle', workspaceId: null, diff --git a/apps/sim/stores/workflows/index.ts b/apps/sim/stores/workflows/index.ts index e2fdf9a7c4a..1571a0e50f1 100644 --- a/apps/sim/stores/workflows/index.ts +++ b/apps/sim/stores/workflows/index.ts @@ -3,7 +3,7 @@ import { getWorkflows } from '@/hooks/queries/utils/workflow-cache' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { mergeSubblockState } from '@/stores/workflows/utils' import { useWorkflowStore } from '@/stores/workflows/workflow/store' -import type { BlockState, WorkflowState } from '@/stores/workflows/workflow/types' +import type { WorkflowState } from '@/stores/workflows/workflow/types' const logger = createLogger('Workflows') @@ -30,9 +30,6 @@ export function getWorkflowWithValues(workflowId: string, workspaceId: string) { return null } - // Get deployment status from registry - const deploymentStatus = useWorkflowRegistry.getState().getWorkflowDeploymentStatus(workflowId) - // Use the current state from the store (only available for active workflow) const workflowState: WorkflowState = useWorkflowStore.getState().getWorkflowState() @@ -52,110 +49,10 @@ export function getWorkflowWithValues(workflowId: string, workspaceId: string) { loops: workflowState.loops, parallels: workflowState.parallels, lastSaved: workflowState.lastSaved, - // Get deployment fields from registry for API compatibility - isDeployed: deploymentStatus?.isDeployed || false, - deployedAt: deploymentStatus?.deployedAt, }, } } -/** - * Get a specific block with its subblock values merged in - * @param blockId ID of the block to retrieve - * @returns The block with merged subblock values or null if not found - */ -export function getBlockWithValues(blockId: string): BlockState | null { - const workflowState = useWorkflowStore.getState() - const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId - - if (!activeWorkflowId || !workflowState.blocks[blockId]) return null - - const mergedBlocks = mergeSubblockState(workflowState.blocks, activeWorkflowId, blockId) - return mergedBlocks[blockId] || null -} - -/** - * Get all workflows with their values merged - * Note: Since localStorage has been removed, this only includes the active workflow state - * @param workspaceId Workspace containing the workflow metadata - * @returns An object containing workflows, with state only for the active workflow - */ -export function getAllWorkflowsWithValues(workspaceId: string) { - const workflows = getWorkflows(workspaceId) - const result: Record< - string, - { - id: string - name: string - description?: string - color: string - folderId?: string | null - workspaceId?: string - apiKey?: string - state: WorkflowState & { isDeployed: boolean; deployedAt?: Date } - } - > = {} - const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId - const currentState = useWorkflowStore.getState() - - // Only sync the active workflow to ensure we always send valid state data - const activeMetadata = activeWorkflowId - ? workflows.find((w) => w.id === activeWorkflowId) - : undefined - if (activeWorkflowId && activeMetadata) { - const metadata = activeMetadata - - // Get deployment status from registry - const deploymentStatus = useWorkflowRegistry - .getState() - .getWorkflowDeploymentStatus(activeWorkflowId) - - // Ensure state has all required fields for Zod validation - const workflowState: WorkflowState = { - ...useWorkflowStore.getState().getWorkflowState(), - // Ensure fallback values for safer handling - blocks: currentState.blocks || {}, - edges: currentState.edges || [], - loops: currentState.loops || {}, - parallels: currentState.parallels || {}, - lastSaved: currentState.lastSaved || Date.now(), - } - - // Merge the subblock values for this specific workflow - const mergedBlocks = mergeSubblockState(workflowState.blocks, activeWorkflowId) - - // Include the API key in the state if it exists in the deployment status - const apiKey = deploymentStatus?.apiKey - - result[activeWorkflowId] = { - id: activeWorkflowId, - name: metadata.name, - description: metadata.description, - color: metadata.color || '#3972F6', - folderId: metadata.folderId, - state: { - blocks: mergedBlocks, - edges: workflowState.edges, - loops: workflowState.loops, - parallels: workflowState.parallels, - lastSaved: workflowState.lastSaved, - // Get deployment fields from registry for API compatibility - isDeployed: deploymentStatus?.isDeployed || false, - deployedAt: deploymentStatus?.deployedAt, - }, - // Include API key if available - apiKey, - } - - // Only include workspaceId if it's not null/undefined - if (metadata.workspaceId) { - result[activeWorkflowId].workspaceId = metadata.workspaceId - } - } - - return result -} - export { useWorkflowRegistry } from '@/stores/workflows/registry/store' export type { WorkflowMetadata } from '@/stores/workflows/registry/types' export { useSubBlockStore } from '@/stores/workflows/subblock/store' diff --git a/apps/sim/stores/workflows/registry/store.ts b/apps/sim/stores/workflows/registry/store.ts index 9ece2c4c18e..59be5bf8b3d 100644 --- a/apps/sim/stores/workflows/registry/store.ts +++ b/apps/sim/stores/workflows/registry/store.ts @@ -3,14 +3,12 @@ import { create } from 'zustand' import { devtools } from 'zustand/middleware' import { DEFAULT_DUPLICATE_OFFSET } from '@/lib/workflows/autolayout/constants' import { getQueryClient } from '@/app/_shell/providers/get-query-client' +import type { WorkflowDeploymentInfo } from '@/hooks/queries/deployments' +import { deploymentKeys } from '@/hooks/queries/deployments' import { invalidateWorkflowLists } from '@/hooks/queries/utils/invalidate-workflow-lists' import { useVariablesStore } from '@/stores/panel/variables/store' import type { Variable } from '@/stores/panel/variables/types' -import type { - DeploymentStatus, - HydrationState, - WorkflowRegistry, -} from '@/stores/workflows/registry/types' +import type { HydrationState, WorkflowRegistry } from '@/stores/workflows/registry/types' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { getUniqueBlockName, regenerateBlockIds } from '@/stores/workflows/utils' import { useWorkflowStore } from '@/stores/workflows/workflow/store' @@ -34,7 +32,6 @@ function resetWorkflowStores() { edges: [], loops: {}, parallels: {}, - deploymentStatuses: {}, lastSaved: Date.now(), }) @@ -48,7 +45,6 @@ export const useWorkflowRegistry = create()( (set, get) => ({ activeWorkflowId: null, error: null, - deploymentStatuses: {}, hydration: initialHydration, clipboard: null, pendingSelection: null, @@ -61,7 +57,6 @@ export const useWorkflowRegistry = create()( set({ activeWorkflowId: null, - deploymentStatuses: {}, error: null, hydration: { phase: 'idle', @@ -73,74 +68,6 @@ export const useWorkflowRegistry = create()( }) }, - getWorkflowDeploymentStatus: (workflowId: string | null): DeploymentStatus | null => { - if (!workflowId) { - workflowId = get().activeWorkflowId - if (!workflowId) return null - } - - const { deploymentStatuses = {} } = get() - - if (deploymentStatuses[workflowId]) { - return deploymentStatuses[workflowId] - } - - return null - }, - - setDeploymentStatus: ( - workflowId: string | null, - isDeployed: boolean, - deployedAt?: Date, - apiKey?: string - ) => { - if (!workflowId) { - workflowId = get().activeWorkflowId - if (!workflowId) return - } - - set((state) => ({ - deploymentStatuses: { - ...state.deploymentStatuses, - [workflowId as string]: { - isDeployed, - deployedAt: deployedAt || (isDeployed ? new Date() : undefined), - apiKey, - needsRedeployment: isDeployed - ? false - : (state.deploymentStatuses?.[workflowId as string]?.needsRedeployment ?? false), - }, - }, - })) - }, - - setWorkflowNeedsRedeployment: (workflowId: string | null, needsRedeployment: boolean) => { - if (!workflowId) { - workflowId = get().activeWorkflowId - if (!workflowId) return - } - - set((state) => { - const deploymentStatuses = state.deploymentStatuses || {} - const currentStatus = deploymentStatuses[workflowId as string] || { isDeployed: false } - - return { - deploymentStatuses: { - ...deploymentStatuses, - [workflowId as string]: { - ...currentStatus, - needsRedeployment, - }, - }, - } - }) - - const { activeWorkflowId } = get() - if (workflowId === activeWorkflowId) { - useWorkflowStore.getState().setNeedsRedeploymentFlag(needsRedeployment) - } - }, - loadWorkflowState: async (workflowId: string) => { const workspaceId = get().hydration.workspaceId if (!workspaceId) { @@ -170,20 +97,19 @@ export const useWorkflowRegistry = create()( } const workflowData = (await response.json()).data - const nextDeploymentStatuses = - workflowData?.isDeployed || workflowData?.deployedAt - ? { - ...get().deploymentStatuses, - [workflowId]: { - isDeployed: workflowData.isDeployed || false, - deployedAt: workflowData.deployedAt - ? new Date(workflowData.deployedAt) - : undefined, - apiKey: workflowData.apiKey || undefined, - needsRedeployment: false, - }, - } - : get().deploymentStatuses + + if (workflowData?.isDeployed !== undefined) { + getQueryClient().setQueryData( + deploymentKeys.info(workflowId), + (prev) => ({ + isDeployed: workflowData.isDeployed ?? false, + deployedAt: workflowData.deployedAt ?? null, + apiKey: workflowData.apiKey ?? prev?.apiKey ?? null, + needsRedeployment: prev?.needsRedeployment ?? false, + isPublicApi: prev?.isPublicApi ?? false, + }) + ) + } let workflowState: WorkflowState @@ -195,7 +121,6 @@ export const useWorkflowRegistry = create()( loops: workflowData.state.loops || {}, parallels: workflowData.state.parallels || {}, lastSaved: Date.now(), - deploymentStatuses: nextDeploymentStatuses, } } else { workflowState = { @@ -204,7 +129,6 @@ export const useWorkflowRegistry = create()( edges: [], loops: {}, parallels: {}, - deploymentStatuses: nextDeploymentStatuses, lastSaved: Date.now(), } @@ -250,7 +174,6 @@ export const useWorkflowRegistry = create()( set((state) => ({ activeWorkflowId: workflowId, error: null, - deploymentStatuses: nextDeploymentStatuses, hydration: { phase: 'ready', workspaceId: state.hydration.workspaceId, @@ -367,7 +290,6 @@ export const useWorkflowRegistry = create()( set({ activeWorkflowId: null, - deploymentStatuses: {}, error: null, hydration: initialHydration, clipboard: null, diff --git a/apps/sim/stores/workflows/registry/types.ts b/apps/sim/stores/workflows/registry/types.ts index f434967e413..96a2d324539 100644 --- a/apps/sim/stores/workflows/registry/types.ts +++ b/apps/sim/stores/workflows/registry/types.ts @@ -1,13 +1,6 @@ import type { Edge } from 'reactflow' import type { BlockState, Loop, Parallel } from '@/stores/workflows/workflow/types' -export interface DeploymentStatus { - isDeployed: boolean - deployedAt?: Date - apiKey?: string - needsRedeployment?: boolean -} - export interface ClipboardData { blocks: Record edges: Edge[] @@ -45,7 +38,6 @@ export interface HydrationState { export interface WorkflowRegistryState { activeWorkflowId: string | null error: string | null - deploymentStatuses: Record hydration: HydrationState clipboard: ClipboardData | null pendingSelection: string[] | null @@ -57,14 +49,6 @@ export interface WorkflowRegistryActions { switchToWorkspace: (id: string) => void markWorkflowCreating: (workflowId: string) => void markWorkflowCreated: (workflowId: string | null) => void - getWorkflowDeploymentStatus: (workflowId: string | null) => DeploymentStatus | null - setDeploymentStatus: ( - workflowId: string | null, - isDeployed: boolean, - deployedAt?: Date, - apiKey?: string - ) => void - setWorkflowNeedsRedeployment: (workflowId: string | null, needsRedeployment: boolean) => void copyBlocks: (blockIds: string[]) => void preparePasteData: (positionOffset?: { x: number; y: number }) => { blocks: Record diff --git a/apps/sim/stores/workflows/workflow/store.ts b/apps/sim/stores/workflows/workflow/store.ts index de8a7b58126..e7900c5a8a2 100644 --- a/apps/sim/stores/workflows/workflow/store.ts +++ b/apps/sim/stores/workflows/workflow/store.ts @@ -107,8 +107,6 @@ const initialState = { loops: {}, parallels: {}, lastSaved: undefined, - deploymentStatuses: {}, - needsRedeployment: false, } export const useWorkflowStore = create()( @@ -116,10 +114,6 @@ export const useWorkflowStore = create()( (set, get) => ({ ...initialState, - setNeedsRedeploymentFlag: (needsRedeployment: boolean) => { - set({ needsRedeployment }) - }, - setCurrentWorkflowId: (currentWorkflowId) => { set({ currentWorkflowId }) }, @@ -540,8 +534,6 @@ export const useWorkflowStore = create()( loops: state.loops, parallels: state.parallels, lastSaved: state.lastSaved, - deploymentStatuses: state.deploymentStatuses, - needsRedeployment: state.needsRedeployment, } }, replaceWorkflowState: ( @@ -580,11 +572,6 @@ export const useWorkflowStore = create()( edges: nextEdges, loops: nextLoops, parallels: nextParallels, - deploymentStatuses: nextState.deploymentStatuses || state.deploymentStatuses, - needsRedeployment: - nextState.needsRedeployment !== undefined - ? nextState.needsRedeployment - : state.needsRedeployment, lastSaved: options?.updateLastSaved === true ? Date.now() @@ -1141,67 +1128,6 @@ export const useWorkflowStore = create()( })) }, - revertToDeployedState: async (deployedState: WorkflowState) => { - const activeWorkflowId = get().currentWorkflowId - - if (!activeWorkflowId) { - logger.error('Cannot revert: no active workflow ID') - return - } - - const deploymentStatus = get().deploymentStatuses?.[activeWorkflowId] - - get().replaceWorkflowState({ - ...deployedState, - needsRedeployment: false, - deploymentStatuses: { - ...get().deploymentStatuses, - ...(deploymentStatus ? { [activeWorkflowId]: deploymentStatus } : {}), - }, - }) - - const values: Record> = {} - Object.entries(deployedState.blocks).forEach(([blockId, block]) => { - values[blockId] = {} - Object.entries(block.subBlocks || {}).forEach(([subBlockId, subBlock]) => { - values[blockId][subBlockId] = subBlock.value - }) - }) - - useSubBlockStore.setState({ - workflowValues: { - ...useSubBlockStore.getState().workflowValues, - [activeWorkflowId]: values, - }, - }) - - get().updateLastSaved() - - // Call API to persist the revert to normalized tables - try { - const response = await fetch( - `/api/workflows/${activeWorkflowId}/deployments/active/revert`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - } - ) - - if (!response.ok) { - const errorData = await response.json() - logger.error('Failed to persist revert to deployed state:', errorData.error) - // Don't throw error to avoid breaking the UI, but log it - } else { - logger.info('Successfully persisted revert to deployed state') - } - } catch (error) { - logger.error('Error calling revert to deployed API:', error) - // Don't throw error to avoid breaking the UI - } - }, - toggleBlockAdvancedMode: (id: string) => { const block = get().blocks[id] if (!block) return diff --git a/apps/sim/stores/workflows/workflow/types.ts b/apps/sim/stores/workflows/workflow/types.ts index 21f22ff5478..37d86847bcb 100644 --- a/apps/sim/stores/workflows/workflow/types.ts +++ b/apps/sim/stores/workflows/workflow/types.ts @@ -1,6 +1,5 @@ import type { Edge } from 'reactflow' import type { OutputFieldDefinition, SubBlockType } from '@/blocks/types' -import type { DeploymentStatus } from '@/stores/workflows/registry/types' export const SUBFLOW_TYPES = { LOOP: 'loop', @@ -173,8 +172,6 @@ export interface WorkflowState { exportedAt?: string } variables?: Record - deploymentStatuses?: Record - needsRedeployment?: boolean dragStartPosition?: DragStartPosition | null } @@ -228,8 +225,6 @@ export interface WorkflowActions { updateParallelType: (parallelId: string, parallelType: 'count' | 'collection') => void generateLoopBlocks: () => Record generateParallelBlocks: () => Record - setNeedsRedeploymentFlag: (needsRedeployment: boolean) => void - revertToDeployedState: (deployedState: WorkflowState) => void toggleBlockAdvancedMode: (id: string) => void setDragStartPosition: (position: DragStartPosition | null) => void getDragStartPosition: () => DragStartPosition | null diff --git a/apps/sim/tools/index.test.ts b/apps/sim/tools/index.test.ts index 3ff33f1c9a0..1c7df7a07c6 100644 --- a/apps/sim/tools/index.test.ts +++ b/apps/sim/tools/index.test.ts @@ -193,8 +193,8 @@ vi.mock('@/tools/registry', () => { return { tools: mockTools } }) -// Mock custom tools - define mock data inside factory function -vi.mock('@/hooks/queries/utils/custom-tool-cache', () => { +// Mock query client for custom tool cache reads +vi.mock('@/app/_shell/providers/get-query-client', () => { const mockCustomTool = { id: 'custom-tool-123', title: 'Custom Weather Tool', @@ -214,13 +214,12 @@ vi.mock('@/hooks/queries/utils/custom-tool-cache', () => { }, } return { - getCustomTool: (toolId: string) => { - if (toolId === 'custom-tool-123') { - return mockCustomTool - } - return undefined - }, - getCustomTools: () => [mockCustomTool], + getQueryClient: () => ({ + getQueryData: (key: string[]) => { + if (key[0] === 'customTools') return [mockCustomTool] + return undefined + }, + }), } })