Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
b19a3dd
feat: implement real-time collaborative workflow editing with Socket.IO
Jun 20, 2025
17b3eb2
feat: complete collaborative subblock editing implementation
Jun 20, 2025
98da771
feat: implement proper authentication for collaborative Socket.IO server
Jun 20, 2025
838695f
feat: complete authentication integration for collaborative Socket.IO…
Jun 20, 2025
540241d
remove sync system and move to server side
Jun 20, 2025
8959327
fix lint
Jun 20, 2025
5a594df
delete unused file
Jun 20, 2025
a9ab750
added socketio dep
waleedlatif1 Jun 20, 2025
f89d531
fix subblock persistence bug
Jun 20, 2025
f1a0d89
working deletion of workflows
Jun 20, 2025
ffecd60
fix lint
Jun 20, 2025
6a6c41d
added railway
waleedlatif1 Jun 20, 2025
7e774aa
add debug logging for railway deployment
waleedlatif1 Jun 21, 2025
c8359f2
improve typing
Jun 21, 2025
4d84fa1
fix lint
Jun 21, 2025
e8caad0
working subflow persistence
Jun 21, 2025
d0d0051
fix lint
Jun 21, 2025
5eba967
working cascade deletion
Jun 21, 2025
66ab790
fix lint
Jun 21, 2025
efdb893
working subflow inside subflow
Jun 21, 2025
1b3aab1
works
Jun 21, 2025
e6d6a44
fix lint
Jun 21, 2025
05be333
prevent subflow in subflow
Jun 21, 2025
5be3b2a
fix lint
Jun 21, 2025
5edf105
add additional logs, add localhost as allowedOrigin
waleedlatif1 Jun 21, 2025
5347abb
add additional logs, add localhost as allowedOrigin
waleedlatif1 Jun 21, 2025
bc72199
fix type error
Jun 21, 2025
7a8c049
remove unused code
Jun 21, 2025
09b12b3
fix lint
Jun 21, 2025
c9f0d6d
fix tests
Jun 21, 2025
dc47334
fix lint
Jun 21, 2025
af1f61d
fix build error
Jun 21, 2025
e0b40c7
workign folder updates
Jun 21, 2025
d8dceac
fix typing issue
Jun 21, 2025
9de5a34
fix lint
Jun 21, 2025
dbb133f
fix typing issues
Jun 21, 2025
af4e5ff
lib/
Jun 21, 2025
5fe1576
fix tests
Jun 21, 2025
01ea300
added old presence component back, updated to use one-time-token bett…
waleedlatif1 Jun 22, 2025
511da56
Merge branch 'main' into feature/realtime-sockets
icecrasher321 Jun 22, 2025
2b9b667
fix errors
Jun 23, 2025
10854e3
fix bugs
Jun 23, 2025
3ac202c
add migration scripts to run
Jun 23, 2025
76f827a
fix lint
Jun 23, 2025
7aff198
fix deploy tests
Jun 23, 2025
56d48e7
fix lint
Jun 23, 2025
4203376
fix minor issues
Jun 23, 2025
fdca2f9
fix lint
Jun 23, 2025
5b3c583
fix migration script
Jun 23, 2025
868b0f2
allow comma separateds id file input to migration script
Jun 23, 2025
835387e
fix lint
Jun 23, 2025
8b76f41
Merge branch 'main' into feature/realtime-sockets
icecrasher321 Jun 23, 2025
cfe7aab
fixed
Jun 23, 2025
46c3936
fix lint
Jun 23, 2025
7f4c740
fix fallback case
Jun 23, 2025
89909f3
fix type errors
Jun 23, 2025
5bbb1cc
Merge branch 'main' into feature/realtime-sockets
icecrasher321 Jun 23, 2025
c08d16e
address greptile comments
Jun 23, 2025
7e533c1
fix lint
Jun 23, 2025
f4e8811
Merge branch 'feature/realtime-sockets' of github.com:simstudioai/sim…
Jun 23, 2025
e65396e
fix script to generate new block ids
Jun 23, 2025
8924c83
fix lint
Jun 23, 2025
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
3 changes: 2 additions & 1 deletion apps/sim/app/(landing)/components/hero-workflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ const mobileEdges: Edge[] = [

const workflowVariants = {
hidden: { opacity: 0, scale: 0.98 },
visible: { opacity: 1, scale: 1, transition: { duration: 0.5, delay: 0.1 } },
visible: { opacity: 1, scale: 1 },
}

export function HeroWorkflow() {
Expand Down Expand Up @@ -208,6 +208,7 @@ export function HeroWorkflow() {
variants={workflowVariants}
initial='hidden'
animate='visible'
transition={{ duration: 0.5, delay: 0.1, ease: 'easeOut' }}
>
<style jsx global>{`
.react-flow__edge-path {
Expand Down
14 changes: 7 additions & 7 deletions apps/sim/app/(landing)/components/nav-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,7 @@ const mobileSheetContainerVariants = {

const mobileNavItemsContainerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
delayChildren: 0.1, // Delay before starting stagger
staggerChildren: 0.08, // Stagger delay between items
},
},
visible: { opacity: 1 },
}

const mobileNavItemVariants = {
Expand Down Expand Up @@ -217,6 +211,7 @@ export default function NavClient({
variants={desktopNavContainerVariants}
initial='hidden'
animate='visible'
transition={{ delay: 0.2, duration: 0.3, ease: 'easeOut' }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Transition config moved here from variant object. Consider extracting shared transition configs into constants for reuse.

>
<NavLinks currentPath={currentPath} onContactClick={onContactClick} />
</motion.div>
Expand Down Expand Up @@ -265,6 +260,7 @@ export default function NavClient({
initial='hidden'
animate='visible'
exit='exit'
transition={{ duration: 0.3, ease: 'easeInOut' }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Mobile sheet animation transition config could be extracted to a shared constant to maintain consistency with other similar animations.

className='fixed inset-y-0 right-0 z-50'
>
<SheetContent
Expand All @@ -281,6 +277,10 @@ export default function NavClient({
variants={mobileNavItemsContainerVariants}
initial='hidden'
animate='visible'
transition={{
delayChildren: 0.1,
staggerChildren: 0.08,
}}
>
<NavLinks
mobile
Expand Down
20 changes: 20 additions & 0 deletions apps/sim/app/api/auth/socket-token/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { headers } from 'next/headers'
import { NextResponse } from 'next/server'
import { auth } from '@/lib/auth'

export async function POST() {
try {
const response = await auth.api.generateOneTimeToken({
headers: await headers(),
})

if (!response) {
return NextResponse.json({ error: 'Failed to generate token' }, { status: 500 })
}

return NextResponse.json({ token: response.token })
} catch (error) {
console.error('Error generating one-time token:', error)
return NextResponse.json({ error: 'Failed to generate token' }, { status: 500 })
}
}
54 changes: 54 additions & 0 deletions apps/sim/app/api/workflows/[id]/deploy/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,33 @@ describe('Workflow Deployment API Route', () => {
}),
}),
})
// Mock normalized table queries (blocks, edges, subflows)
.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([
{
id: 'block-1',
type: 'starter',
name: 'Start',
positionX: '100',
positionY: '100',
enabled: true,
subBlocks: {},
data: {},
},
]),
}),
})
.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([]), // No edges
}),
})
.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([]), // No subflows
}),
})
.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
Expand Down Expand Up @@ -189,6 +216,33 @@ describe('Workflow Deployment API Route', () => {
}),
}),
})
// Mock normalized table queries (blocks, edges, subflows)
.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([
{
id: 'block-1',
type: 'starter',
name: 'Start',
positionX: '100',
positionY: '100',
enabled: true,
subBlocks: {},
data: {},
},
]),
}),
})
.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([]), // No edges
}),
})
.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
where: vi.fn().mockResolvedValue([]), // No subflows
}),
})
Comment thread
icecrasher321 marked this conversation as resolved.
.mockReturnValueOnce({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
Expand Down
98 changes: 94 additions & 4 deletions apps/sim/app/api/workflows/[id]/deploy/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { v4 as uuidv4 } from 'uuid'
import { createLogger } from '@/lib/logs/console-logger'
import { generateApiKey } from '@/lib/utils'
import { db } from '@/db'
import { apiKey, workflow } from '@/db/schema'
import { apiKey, workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@/db/schema'
import { validateWorkflowAccess } from '../../middleware'
import { createErrorResponse, createSuccessResponse } from '../../utils'

Expand Down Expand Up @@ -126,7 +126,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
return createErrorResponse(validation.error.message, validation.error.status)
}

// Get the workflow to find the user and current state
// Get the workflow to find the user
const workflowData = await db
.select({
userId: workflow.userId,
Expand All @@ -142,8 +142,92 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
}

const userId = workflowData[0].userId
const currentState = workflowData[0].state

// Get the current live state from normalized tables instead of stale JSON
logger.debug(`[${requestId}] Getting current workflow state for deployment`)

// Get blocks from normalized table
const blocks = await db.select().from(workflowBlocks).where(eq(workflowBlocks.workflowId, id))

// Get edges from normalized table
const edges = await db.select().from(workflowEdges).where(eq(workflowEdges.workflowId, id))

// Get subflows from normalized table
const subflows = await db
.select()
.from(workflowSubflows)
.where(eq(workflowSubflows.workflowId, id))

// Build current state from normalized data
const blocksMap: Record<string, any> = {}
const loops: Record<string, any> = {}
const parallels: Record<string, any> = {}

// Process blocks
blocks.forEach((block) => {
blocksMap[block.id] = {
id: block.id,
type: block.type,
name: block.name,
position: { x: Number(block.positionX), y: Number(block.positionY) },
data: block.data,
enabled: block.enabled,
subBlocks: block.subBlocks || {},
}
})

// Process subflows (loops and parallels)
subflows.forEach((subflow) => {
const config = (subflow.config as any) || {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Type assertion to 'any' should be avoided. Consider defining proper interface for config

if (subflow.type === 'loop') {
loops[subflow.id] = {
nodes: config.nodes || [],
iterationCount: config.iterationCount || 1,
iterationType: config.iterationType || 'fixed',
collection: config.collection || '',
}
} else if (subflow.type === 'parallel') {
parallels[subflow.id] = {
nodes: config.nodes || [],
parallelCount: config.parallelCount || 2,
collection: config.collection || '',
}
}
})

// Convert edges to the expected format
const edgesArray = edges.map((edge) => ({
id: edge.id,
source: edge.sourceBlockId,
target: edge.targetBlockId,
sourceHandle: edge.sourceHandle,
targetHandle: edge.targetHandle,
type: 'default',
data: {},
}))

const currentState = {
blocks: blocksMap,
edges: edgesArray,
loops,
parallels,
lastSaved: Date.now(),
}

logger.debug(`[${requestId}] Current state retrieved from normalized tables:`, {
blocksCount: Object.keys(blocksMap).length,
edgesCount: edgesArray.length,
loopsCount: Object.keys(loops).length,
parallelsCount: Object.keys(parallels).length,
})

if (!currentState || !currentState.blocks) {
logger.error(`[${requestId}] Invalid workflow state retrieved`, { currentState })
throw new Error('Invalid workflow state: missing blocks')
}

const deployedAt = new Date()
logger.debug(`[${requestId}] Proceeding with deployment at ${deployedAt.toISOString()}`)

// Check if the user already has an API key
const userApiKey = await db
Expand Down Expand Up @@ -191,7 +275,13 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
logger.info(`[${requestId}] Workflow deployed successfully: ${id}`)
return createSuccessResponse({ apiKey: userKey, isDeployed: true, deployedAt })
} catch (error: any) {
logger.error(`[${requestId}] Error deploying workflow: ${id}`, error)
logger.error(`[${requestId}] Error deploying workflow: ${id}`, {
error: error.message,
stack: error.stack,
name: error.name,
cause: error.cause,
fullError: error,
})
return createErrorResponse(error.message || 'Failed to deploy workflow', 500)
}
}
Expand Down
Loading