mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(dbsync): only sync with db on exit
This commit is contained in:
@@ -19,7 +19,6 @@ import { initializeStateLogger } from '@/stores/workflow/logger'
|
||||
import { useWorkflowRegistry } from '@/stores/workflow/registry/store'
|
||||
import { useWorkflowStore } from '@/stores/workflow/store'
|
||||
import { NotificationList } from '@/app/w/components/notifications/notifications'
|
||||
import { WorkflowSyncWrapper } from '@/app/w/components/workflows/sync-wrapper'
|
||||
import { getBlock } from '../../../blocks'
|
||||
import { ErrorBoundary } from '../components/error-boundary/error-boundary'
|
||||
import { CustomEdge } from './components/custom-edge/custom-edge'
|
||||
@@ -379,9 +378,7 @@ export default function Workflow() {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<ErrorBoundary>
|
||||
<WorkflowSyncWrapper>
|
||||
<WorkflowContent />
|
||||
</WorkflowSyncWrapper>
|
||||
<WorkflowContent />
|
||||
</ErrorBoundary>
|
||||
</ReactFlowProvider>
|
||||
)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { ReactNode } from 'react'
|
||||
import {
|
||||
useDebouncedWorkflowSync,
|
||||
usePeriodicWorkflowSync,
|
||||
useSyncOnUnload,
|
||||
} from '@/stores/workflow/sync/hooks'
|
||||
|
||||
interface WorkflowSyncWrapperProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export function WorkflowSyncWrapper({ children }: WorkflowSyncWrapperProps) {
|
||||
useDebouncedWorkflowSync()
|
||||
usePeriodicWorkflowSync()
|
||||
useSyncOnUnload()
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
+1
-1
@@ -5,6 +5,6 @@ export default {
|
||||
out: './db/migrations',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!,
|
||||
url: 'postgresql://postgres:%23WaldoEmmy1@db.jchdgebatsqopodtyast.supabase.co:5432/postgres',
|
||||
},
|
||||
} satisfies Config
|
||||
|
||||
+1
-1
@@ -11,5 +11,5 @@ export async function middleware(request: NextRequest) {
|
||||
|
||||
// TODO: Add protected routes
|
||||
export const config = {
|
||||
matcher: ['/dashboard/:path*', '/w/:path*'],
|
||||
matcher: ['/w/:path*'],
|
||||
}
|
||||
|
||||
@@ -4,9 +4,15 @@ import { useExecutionStore } from './execution/store'
|
||||
import { useNotificationStore } from './notifications/store'
|
||||
import { useEnvironmentStore } from './settings/environment/store'
|
||||
import { useGeneralStore } from './settings/general/store'
|
||||
import { initializeSyncManager } from './sync-manager'
|
||||
import { useWorkflowRegistry } from './workflow/registry/store'
|
||||
import { useWorkflowStore } from './workflow/store'
|
||||
|
||||
// Initialize sync manager when the store is first imported
|
||||
if (typeof window !== 'undefined') {
|
||||
initializeSyncManager()
|
||||
}
|
||||
|
||||
// Reset all application stores to their initial state
|
||||
export const resetAllStores = () => {
|
||||
// Selectively clear localStorage items
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { useWorkflowRegistry } from './workflow/registry/store'
|
||||
import { useWorkflowStore } from './workflow/store'
|
||||
|
||||
interface SyncPayload {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
state: string
|
||||
}
|
||||
|
||||
async function syncWorkflowToServer(payload: SyncPayload): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch('/api/workflows/sync', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
keepalive: true, // Ensures request completes even during page unload
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
window.location.href = '/login'
|
||||
return false
|
||||
}
|
||||
throw new Error(`Sync failed: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Error syncing workflow:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function initializeSyncManager() {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
const handleBeforeUnload = async (event: BeforeUnloadEvent) => {
|
||||
const { activeWorkflowId, workflows } = useWorkflowRegistry.getState()
|
||||
const workflowState = useWorkflowStore.getState()
|
||||
|
||||
if (!activeWorkflowId || !workflows[activeWorkflowId]) {
|
||||
return
|
||||
}
|
||||
|
||||
const activeWorkflow = workflows[activeWorkflowId]
|
||||
const payload: SyncPayload = {
|
||||
id: activeWorkflowId,
|
||||
name: activeWorkflow.name,
|
||||
description: activeWorkflow.description,
|
||||
state: JSON.stringify({
|
||||
blocks: workflowState.blocks,
|
||||
edges: workflowState.edges,
|
||||
loops: workflowState.loops,
|
||||
lastSaved: workflowState.lastSaved,
|
||||
}),
|
||||
}
|
||||
|
||||
// Show confirmation dialog
|
||||
event.preventDefault()
|
||||
event.returnValue = ''
|
||||
|
||||
// Attempt to sync
|
||||
await syncWorkflowToServer(payload)
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import debounce from 'lodash.debounce'
|
||||
import { useNotificationStore } from '@/stores/notifications/store'
|
||||
import { useWorkflowRegistry } from '../registry/store'
|
||||
import { useWorkflowStore } from '../store'
|
||||
|
||||
const SYNC_DEBOUNCE_MS = 2000 // 2 seconds
|
||||
const PERIODIC_SYNC_MS = 30000 // 30 seconds
|
||||
|
||||
interface SyncPayload {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
state: string
|
||||
}
|
||||
|
||||
async function syncWorkflowToServer(payload: SyncPayload): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch('/api/workflows/sync', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
// Auth error - will be handled by the middleware
|
||||
window.location.href = '/login'
|
||||
return false
|
||||
}
|
||||
throw new Error(`Sync failed: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Error syncing workflow:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function useDebouncedWorkflowSync() {
|
||||
const router = useRouter()
|
||||
const { addNotification } = useNotificationStore()
|
||||
const workflowState = useWorkflowStore((state) => ({
|
||||
blocks: state.blocks,
|
||||
edges: state.edges,
|
||||
loops: state.loops,
|
||||
lastSaved: state.lastSaved,
|
||||
}))
|
||||
const { activeWorkflowId, workflows } = useWorkflowRegistry()
|
||||
|
||||
const debouncedSyncRef = useRef<ReturnType<typeof debounce> | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeWorkflowId || !workflows[activeWorkflowId]) return
|
||||
|
||||
const syncWorkflow = async () => {
|
||||
const activeWorkflow = workflows[activeWorkflowId]
|
||||
const payload: SyncPayload = {
|
||||
id: activeWorkflowId,
|
||||
name: activeWorkflow.name,
|
||||
description: activeWorkflow.description,
|
||||
state: JSON.stringify(workflowState),
|
||||
}
|
||||
|
||||
const success = await syncWorkflowToServer(payload)
|
||||
if (!success) {
|
||||
addNotification(
|
||||
'error',
|
||||
'Failed to save workflow changes. Please try again.',
|
||||
activeWorkflowId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a debounced version of syncWorkflow
|
||||
if (!debouncedSyncRef.current) {
|
||||
debouncedSyncRef.current = debounce(syncWorkflow, SYNC_DEBOUNCE_MS)
|
||||
}
|
||||
|
||||
// Call the debounced sync
|
||||
debouncedSyncRef.current()
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
debouncedSyncRef.current?.cancel()
|
||||
}
|
||||
}, [activeWorkflowId, workflows, workflowState, addNotification])
|
||||
}
|
||||
|
||||
export function usePeriodicWorkflowSync() {
|
||||
const { addNotification } = useNotificationStore()
|
||||
const workflowState = useWorkflowStore((state) => ({
|
||||
blocks: state.blocks,
|
||||
edges: state.edges,
|
||||
loops: state.loops,
|
||||
lastSaved: state.lastSaved,
|
||||
}))
|
||||
const { activeWorkflowId, workflows } = useWorkflowRegistry()
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeWorkflowId || !workflows[activeWorkflowId]) return
|
||||
|
||||
const syncWorkflow = async () => {
|
||||
const activeWorkflow = workflows[activeWorkflowId]
|
||||
const payload: SyncPayload = {
|
||||
id: activeWorkflowId,
|
||||
name: activeWorkflow.name,
|
||||
description: activeWorkflow.description,
|
||||
state: JSON.stringify(workflowState),
|
||||
}
|
||||
|
||||
const success = await syncWorkflowToServer(payload)
|
||||
if (!success) {
|
||||
addNotification(
|
||||
'error',
|
||||
'Failed to auto-save workflow changes. Please save manually.',
|
||||
activeWorkflowId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const intervalId = setInterval(syncWorkflow, PERIODIC_SYNC_MS)
|
||||
|
||||
return () => clearInterval(intervalId)
|
||||
}, [activeWorkflowId, workflows, workflowState, addNotification])
|
||||
}
|
||||
|
||||
export function useSyncOnUnload() {
|
||||
const { addNotification } = useNotificationStore()
|
||||
const workflowState = useWorkflowStore((state) => ({
|
||||
blocks: state.blocks,
|
||||
edges: state.edges,
|
||||
loops: state.loops,
|
||||
lastSaved: state.lastSaved,
|
||||
}))
|
||||
const { activeWorkflowId, workflows } = useWorkflowRegistry()
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeWorkflowId || !workflows[activeWorkflowId]) return
|
||||
|
||||
const handleBeforeUnload = async (event: BeforeUnloadEvent) => {
|
||||
const activeWorkflow = workflows[activeWorkflowId]
|
||||
const payload: SyncPayload = {
|
||||
id: activeWorkflowId,
|
||||
name: activeWorkflow.name,
|
||||
description: activeWorkflow.description,
|
||||
state: JSON.stringify(workflowState),
|
||||
}
|
||||
|
||||
// Use the keepalive option to try to complete the request even during unload
|
||||
const response = await fetch('/api/workflows/sync', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
keepalive: true,
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
addNotification(
|
||||
'error',
|
||||
'Failed to save workflow changes before closing.',
|
||||
activeWorkflowId
|
||||
)
|
||||
}
|
||||
|
||||
// Show a confirmation dialog
|
||||
event.preventDefault()
|
||||
event.returnValue = ''
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', handleBeforeUnload)
|
||||
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
|
||||
}, [activeWorkflowId, workflows, workflowState, addNotification])
|
||||
}
|
||||
Reference in New Issue
Block a user