Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { useParams } from 'next/navigation'
import { SELECTOR_CONTEXT_FIELDS } from '@/lib/workflows/subblocks/context'
import type { SubBlockConfig } from '@/blocks/types'
import { extractEnvVarName, isEnvVarReference, isReference } from '@/executor/constants'
import { usePersonalEnvironment } from '@/hooks/queries/environment'
import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types'
import { useEnvironmentStore } from '@/stores/settings/environment'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useDependsOnGate } from './use-depends-on-gate'
import { useSubBlockValue } from './use-sub-block-value'
Expand All @@ -32,7 +32,7 @@ export function useSelectorSetup(
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId)
const workflowId = (params?.workflowId as string) || activeWorkflowId || ''

const envVariables = useEnvironmentStore((s) => s.variables)
const { data: envVariables = {} } = usePersonalEnvironment()

const { finalDisabled, dependencyValues, canonicalIndex } = useDependsOnGate(
blockId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { useVariablesStore as usePanelVariablesStore } from '@/stores/panel'
import type { Variable } from '@/stores/panel/variables/types'
import {
getVariablesPosition,
MAX_VARIABLES_HEIGHT,
Expand All @@ -36,7 +37,6 @@ import {
MIN_VARIABLES_WIDTH,
useVariablesStore,
} from '@/stores/variables/store'
import type { Variable } from '@/stores/variables/types'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import { WorkflowValidationError } from '@/serializer'
import { useCurrentWorkflowExecution, useExecutionStore } from '@/stores/execution'
import { useNotificationStore } from '@/stores/notifications'
import { useVariablesStore } from '@/stores/panel'
import { useEnvironmentStore } from '@/stores/settings/environment'
import {
clearExecutionPointer,
consolePersistence,
Expand Down Expand Up @@ -120,7 +119,6 @@ export function useWorkflowExecution() {
}))
)
const hasHydrated = useTerminalConsoleStore((s) => s._hasHydrated)
const getAllVariables = useEnvironmentStore((s) => s.getAllVariables)
const { getVariablesByWorkflowId, variables } = useVariablesStore(
useShallow((s) => ({
getVariablesByWorkflowId: s.getVariablesByWorkflowId,
Expand Down Expand Up @@ -744,7 +742,6 @@ export function useWorkflowExecution() {
activeWorkflowId,
currentWorkflow,
toggleConsole,
getAllVariables,
getVariablesByWorkflowId,
setIsExecuting,
setIsDebugging,
Expand Down
17 changes: 1 addition & 16 deletions apps/sim/hooks/queries/environment.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { useEffect } from 'react'
import { createLogger } from '@sim/logger'
import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import type { WorkspaceEnvironmentData } from '@/lib/environment/api'
import { fetchPersonalEnvironment, fetchWorkspaceEnvironment } from '@/lib/environment/api'
import { workspaceCredentialKeys } from '@/hooks/queries/credentials'
import { API_ENDPOINTS } from '@/stores/constants'
import type { EnvironmentVariable } from '@/stores/settings/environment'
import { useEnvironmentStore } from '@/stores/settings/environment'

export type { WorkspaceEnvironmentData } from '@/lib/environment/api'
export type { EnvironmentVariable } from '@/stores/settings/environment'
Expand All @@ -22,29 +20,16 @@ export const environmentKeys = {
workspace: (workspaceId: string) => [...environmentKeys.all, 'workspace', workspaceId] as const,
}

/**
* Environment Variable Types
*/
/**
* Hook to fetch personal environment variables
*/
export function usePersonalEnvironment() {
const setVariables = useEnvironmentStore((state) => state.setVariables)

const query = useQuery({
return useQuery({
queryKey: environmentKeys.personal(),
queryFn: ({ signal }) => fetchPersonalEnvironment(signal),
staleTime: 60 * 1000, // 1 minute
placeholderData: keepPreviousData,
})

useEffect(() => {
if (query.data) {
setVariables(query.data)
}
}, [query.data, setVariables])

return query
}

/**
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/hooks/selectors/use-selector-query.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import { extractEnvVarName, isEnvVarReference, isReference } from '@/executor/constants'
import { usePersonalEnvironment } from '@/hooks/queries/environment'
import { getSelectorDefinition, mergeOption } from '@/hooks/selectors/registry'
import type { SelectorKey, SelectorOption, SelectorQueryArgs } from '@/hooks/selectors/types'
import { useEnvironmentStore } from '@/stores/settings/environment'

interface SelectorHookArgs extends Omit<SelectorQueryArgs, 'key'> {
search?: string
Expand Down Expand Up @@ -31,7 +31,7 @@ export function useSelectorOptionDetail(
key: SelectorKey,
args: SelectorHookArgs & { detailId?: string }
) {
const envVariables = useEnvironmentStore((s) => s.variables)
const { data: envVariables = {} } = usePersonalEnvironment()
const definition = getSelectorDefinition(key)

const resolvedDetailId = useMemo(() => {
Expand Down
224 changes: 21 additions & 203 deletions apps/sim/stores/index.ts
Original file line number Diff line number Diff line change
@@ -1,205 +1,20 @@
'use client'

import { useEffect } from 'react'
import { createLogger } from '@sim/logger'
import { getQueryClient } from '@/app/_shell/providers/get-query-client'
import { environmentKeys } from '@/hooks/queries/environment'
import { useExecutionStore } from '@/stores/execution'
import { useVariablesStore } from '@/stores/panel'
import { useEnvironmentStore } from '@/stores/settings/environment'
import { consolePersistence, useTerminalConsoleStore } from '@/stores/terminal'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { useWorkflowStore } from '@/stores/workflows/workflow/store'

const logger = createLogger('Stores')

// Track initialization state
let isInitializing = false
let appFullyInitialized = false
let dataInitialized = false // Flag for actual data loading completion

/**
* Initialize the application state and sync system
* localStorage persistence has been removed - relies on DB and Zustand stores only
*/
async function initializeApplication(): Promise<void> {
if (typeof window === 'undefined' || isInitializing) return

isInitializing = true
appFullyInitialized = false

// Track initialization start time
const initStartTime = Date.now()

try {
// Load environment variables directly from DB
await useEnvironmentStore.getState().loadEnvironmentVariables()

// Mark data as initialized only after sync managers have loaded data from DB
dataInitialized = true

// Log initialization timing information
const initDuration = Date.now() - initStartTime
logger.info(`Application initialization completed in ${initDuration}ms`)

// Mark application as fully initialized
appFullyInitialized = true
} catch (error) {
logger.error('Error during application initialization:', { error })
// Still mark as initialized to prevent being stuck in initializing state
appFullyInitialized = true
// But don't mark data as initialized on error
dataInitialized = false
} finally {
isInitializing = false
}
}

/**
* Checks if application is fully initialized
*/
export function isAppInitialized(): boolean {
return appFullyInitialized
}

/**
* Checks if data has been loaded from the database
* This should be checked before any sync operations
*/
export function isDataInitialized(): boolean {
return dataInitialized
}

/**
* Handle application cleanup before unload
*/
function handleBeforeUnload(event: BeforeUnloadEvent): void {
// Check if we're on an authentication page and skip confirmation if we are
if (typeof window !== 'undefined') {
const path = window.location.pathname
// Skip confirmation for auth-related pages
if (
path === '/login' ||
path === '/signup' ||
path === '/reset-password' ||
path === '/verify'
) {
return
}
}

// Standard beforeunload pattern
event.preventDefault()
event.returnValue = ''
}

/**
* Clean up sync system
*/
function cleanupApplication(): void {
window.removeEventListener('beforeunload', handleBeforeUnload)
// Note: No sync managers to dispose - Socket.IO handles cleanup
}

/**
* Clear all user data when signing out
* localStorage persistence has been removed
*/
export async function clearUserData(): Promise<void> {
if (typeof window === 'undefined') return

try {
// Note: No sync managers to dispose - Socket.IO handles cleanup

// Reset all stores to their initial state
resetAllStores()

// Clear localStorage except for essential app settings (minimal usage)
const keysToKeep = ['next-favicon', 'theme']
const keysToRemove = Object.keys(localStorage).filter((key) => !keysToKeep.includes(key))
keysToRemove.forEach((key) => localStorage.removeItem(key))

// Reset application initialization state
appFullyInitialized = false
dataInitialized = false

logger.info('User data cleared successfully')
} catch (error) {
logger.error('Error clearing user data:', { error })
}
}

/**
* Hook to manage application lifecycle
*/
export function useAppInitialization() {
useEffect(() => {
// Use Promise to handle async initialization
initializeApplication()

return () => {
cleanupApplication()
}
}, [])
}

/**
* Hook to reinitialize the application after successful login
* Use this in the login success handler or post-login page
*/
export function useLoginInitialization() {
useEffect(() => {
reinitializeAfterLogin()
}, [])
}

/**
* Reinitialize the application after login
* This ensures we load fresh data from the database for the new user
* Reset all Zustand stores and React Query caches to initial state.
*/
export async function reinitializeAfterLogin(): Promise<void> {
if (typeof window === 'undefined') return

try {
// Reset application initialization state
appFullyInitialized = false
dataInitialized = false

// Note: No sync managers to dispose - Socket.IO handles cleanup

// Clean existing state to avoid stale data
resetAllStores()

// Reset initialization flags to force a fresh load
isInitializing = false

// Reinitialize the application
await initializeApplication()

logger.info('Application reinitialized after login')
} catch (error) {
logger.error('Error reinitializing application:', { error })
}
}

// Initialize immediately when imported on client
if (typeof window !== 'undefined') {
initializeApplication()
}

// Export all stores
export {
useWorkflowStore,
useWorkflowRegistry,
useEnvironmentStore,
useExecutionStore,
useTerminalConsoleStore,
useVariablesStore,
useSubBlockStore,
}

// Helper function to reset all stores
export const resetAllStores = () => {
// Reset all stores to initial state
useWorkflowRegistry.setState({
activeWorkflowId: null,
error: null,
Expand All @@ -214,7 +29,7 @@ export const resetAllStores = () => {
})
useWorkflowStore.getState().clear()
useSubBlockStore.getState().clear()
useEnvironmentStore.getState().reset()
getQueryClient().removeQueries({ queryKey: environmentKeys.all })
useExecutionStore.getState().reset()
useTerminalConsoleStore.setState({
workflowEntries: {},
Expand All @@ -223,21 +38,24 @@ export const resetAllStores = () => {
isOpen: false,
})
consolePersistence.persist()
// Custom tools are managed by React Query cache, not a Zustand store
// Variables store has no tracking to reset; registry hydrates
}

// Helper function to log all store states
export const logAllStores = () => {
const state = {
workflow: useWorkflowStore.getState(),
workflowRegistry: useWorkflowRegistry.getState(),
environment: useEnvironmentStore.getState(),
execution: useExecutionStore.getState(),
console: useTerminalConsoleStore.getState(),
subBlock: useSubBlockStore.getState(),
variables: useVariablesStore.getState(),
}
/**
* Clear all user data when signing out.
*/
export async function clearUserData(): Promise<void> {
if (typeof window === 'undefined') return

return state
try {
resetAllStores()

// Clear localStorage except for essential app settings
const keysToKeep = ['next-favicon', 'theme']
const keysToRemove = Object.keys(localStorage).filter((key) => !keysToKeep.includes(key))
keysToRemove.forEach((key) => localStorage.removeItem(key))

logger.info('User data cleared successfully')
} catch (error) {
logger.error('Error clearing user data:', { error })
}
}
Loading
Loading