Skip to content

Commit 8748e1d

Browse files
authored
improvement(db): remove deprecated 'state' column from workflow table (#994)
* improvement(db): remove deprecated column from workflow table * removed extraneous logs * update sockets envvar
1 parent 133a32e commit 8748e1d

File tree

17 files changed

+6026
-720
lines changed

17 files changed

+6026
-720
lines changed

apps/sim/app/api/templates/[id]/use/route.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
8080
workspaceId: workspaceId,
8181
name: `${templateData.name} (copy)`,
8282
description: templateData.description,
83-
state: templateData.state,
8483
color: templateData.color,
8584
userId: session.user.id,
8685
createdAt: now,
@@ -158,9 +157,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
158157
}))
159158
}
160159

161-
// Update the workflow with the corrected state
162-
await tx.update(workflow).set({ state: updatedState }).where(eq(workflow.id, newWorkflowId))
163-
164160
// Insert blocks and edges
165161
if (blockEntries.length > 0) {
166162
await tx.insert(workflowBlocks).values(blockEntries)

apps/sim/app/api/workflows/[id]/duplicate/route.ts

Lines changed: 2 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { createLogger } from '@/lib/logs/console/logger'
77
import { getUserEntityPermissions } from '@/lib/permissions/utils'
88
import { db } from '@/db'
99
import { workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@/db/schema'
10-
import type { LoopConfig, ParallelConfig, WorkflowState } from '@/stores/workflows/workflow/types'
10+
import type { LoopConfig, ParallelConfig } from '@/stores/workflows/workflow/types'
1111

1212
const logger = createLogger('WorkflowDuplicateAPI')
1313

@@ -90,7 +90,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
9090
folderId: folderId || source.folderId,
9191
name,
9292
description: description || source.description,
93-
state: source.state, // We'll update this later with new block IDs
9493
color: color || source.color,
9594
lastSynced: now,
9695
createdAt: now,
@@ -112,9 +111,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
112111
// Create a mapping from old block IDs to new block IDs
113112
const blockIdMapping = new Map<string, string>()
114113

115-
// Initialize state for updating with new block IDs
116-
let updatedState: WorkflowState = source.state as WorkflowState
117-
118114
if (sourceBlocks.length > 0) {
119115
// First pass: Create all block ID mappings
120116
sourceBlocks.forEach((block) => {
@@ -265,86 +261,10 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
265261
)
266262
}
267263

268-
// Update the JSON state to use new block IDs
269-
if (updatedState && typeof updatedState === 'object') {
270-
updatedState = JSON.parse(JSON.stringify(updatedState)) as WorkflowState
271-
272-
// Update blocks object keys
273-
if (updatedState.blocks && typeof updatedState.blocks === 'object') {
274-
const newBlocks = {} as Record<string, (typeof updatedState.blocks)[string]>
275-
for (const [oldId, blockData] of Object.entries(updatedState.blocks)) {
276-
const newId = blockIdMapping.get(oldId) || oldId
277-
newBlocks[newId] = {
278-
...blockData,
279-
id: newId,
280-
// Update data.parentId and extent in the JSON state as well
281-
data: (() => {
282-
const block = blockData as any
283-
if (block.data && typeof block.data === 'object' && block.data.parentId) {
284-
return {
285-
...block.data,
286-
parentId: blockIdMapping.get(block.data.parentId) || block.data.parentId,
287-
extent: 'parent', // Ensure extent is set for child blocks
288-
}
289-
}
290-
return block.data
291-
})(),
292-
}
293-
}
294-
updatedState.blocks = newBlocks
295-
}
296-
297-
// Update edges array
298-
if (updatedState.edges && Array.isArray(updatedState.edges)) {
299-
updatedState.edges = updatedState.edges.map((edge) => ({
300-
...edge,
301-
id: crypto.randomUUID(),
302-
source: blockIdMapping.get(edge.source) || edge.source,
303-
target: blockIdMapping.get(edge.target) || edge.target,
304-
}))
305-
}
306-
307-
// Update loops and parallels if they exist
308-
if (updatedState.loops && typeof updatedState.loops === 'object') {
309-
const newLoops = {} as Record<string, (typeof updatedState.loops)[string]>
310-
for (const [oldId, loopData] of Object.entries(updatedState.loops)) {
311-
const newId = blockIdMapping.get(oldId) || oldId
312-
const loopConfig = loopData as any
313-
newLoops[newId] = {
314-
...loopConfig,
315-
id: newId,
316-
// Update node references in loop config
317-
nodes: loopConfig.nodes
318-
? loopConfig.nodes.map((nodeId: string) => blockIdMapping.get(nodeId) || nodeId)
319-
: [],
320-
}
321-
}
322-
updatedState.loops = newLoops
323-
}
324-
325-
if (updatedState.parallels && typeof updatedState.parallels === 'object') {
326-
const newParallels = {} as Record<string, (typeof updatedState.parallels)[string]>
327-
for (const [oldId, parallelData] of Object.entries(updatedState.parallels)) {
328-
const newId = blockIdMapping.get(oldId) || oldId
329-
const parallelConfig = parallelData as any
330-
newParallels[newId] = {
331-
...parallelConfig,
332-
id: newId,
333-
// Update node references in parallel config
334-
nodes: parallelConfig.nodes
335-
? parallelConfig.nodes.map((nodeId: string) => blockIdMapping.get(nodeId) || nodeId)
336-
: [],
337-
}
338-
}
339-
updatedState.parallels = newParallels
340-
}
341-
}
342-
343-
// Update the workflow state with the new block IDs
264+
// Update the workflow timestamp
344265
await tx
345266
.update(workflow)
346267
.set({
347-
state: updatedState,
348268
updatedAt: now,
349269
})
350270
.where(eq(workflow.id, newWorkflowId))

apps/sim/app/api/workflows/[id]/route.test.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,14 @@ describe('Workflow By ID API Route', () => {
8989
userId: 'user-123',
9090
name: 'Test Workflow',
9191
workspaceId: null,
92-
state: { blocks: {}, edges: [] },
92+
}
93+
94+
const mockNormalizedData = {
95+
blocks: {},
96+
edges: [],
97+
loops: {},
98+
parallels: {},
99+
isFromNormalizedTables: true,
93100
}
94101

95102
vi.doMock('@/lib/auth', () => ({
@@ -110,6 +117,10 @@ describe('Workflow By ID API Route', () => {
110117
},
111118
}))
112119

120+
vi.doMock('@/lib/workflows/db-helpers', () => ({
121+
loadWorkflowFromNormalizedTables: vi.fn().mockResolvedValue(mockNormalizedData),
122+
}))
123+
113124
const req = new NextRequest('http://localhost:3000/api/workflows/workflow-123')
114125
const params = Promise.resolve({ id: 'workflow-123' })
115126

@@ -127,7 +138,14 @@ describe('Workflow By ID API Route', () => {
127138
userId: 'other-user',
128139
name: 'Test Workflow',
129140
workspaceId: 'workspace-456',
130-
state: { blocks: {}, edges: [] },
141+
}
142+
143+
const mockNormalizedData = {
144+
blocks: {},
145+
edges: [],
146+
loops: {},
147+
parallels: {},
148+
isFromNormalizedTables: true,
131149
}
132150

133151
vi.doMock('@/lib/auth', () => ({
@@ -148,6 +166,10 @@ describe('Workflow By ID API Route', () => {
148166
},
149167
}))
150168

169+
vi.doMock('@/lib/workflows/db-helpers', () => ({
170+
loadWorkflowFromNormalizedTables: vi.fn().mockResolvedValue(mockNormalizedData),
171+
}))
172+
151173
vi.doMock('@/lib/permissions/utils', () => ({
152174
getUserEntityPermissions: vi.fn().mockResolvedValue('read'),
153175
hasAdminPermission: vi.fn().mockResolvedValue(false),
@@ -170,7 +192,6 @@ describe('Workflow By ID API Route', () => {
170192
userId: 'other-user',
171193
name: 'Test Workflow',
172194
workspaceId: 'workspace-456',
173-
state: { blocks: {}, edges: [] },
174195
}
175196

176197
vi.doMock('@/lib/auth', () => ({
@@ -213,7 +234,6 @@ describe('Workflow By ID API Route', () => {
213234
userId: 'user-123',
214235
name: 'Test Workflow',
215236
workspaceId: null,
216-
state: { blocks: {}, edges: [] },
217237
}
218238

219239
const mockNormalizedData = {

apps/sim/app/api/workflows/[id]/route.ts

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
120120
logger.debug(`[${requestId}] Attempting to load workflow ${workflowId} from normalized tables`)
121121
const normalizedData = await loadWorkflowFromNormalizedTables(workflowId)
122122

123-
const finalWorkflowData = { ...workflowData }
124-
125123
if (normalizedData) {
126124
logger.debug(`[${requestId}] Found normalized data for workflow ${workflowId}:`, {
127125
blocksCount: Object.keys(normalizedData.blocks).length,
@@ -131,38 +129,31 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
131129
loops: normalizedData.loops,
132130
})
133131

134-
// Use normalized table data - reconstruct complete state object
135-
// First get any existing state properties, then override with normalized data
136-
const existingState =
137-
workflowData.state && typeof workflowData.state === 'object' ? workflowData.state : {}
138-
139-
finalWorkflowData.state = {
140-
// Default values for expected properties
141-
deploymentStatuses: {},
142-
hasActiveWebhook: false,
143-
// Preserve any existing state properties
144-
...existingState,
145-
// Override with normalized data (this takes precedence)
146-
blocks: normalizedData.blocks,
147-
edges: normalizedData.edges,
148-
loops: normalizedData.loops,
149-
parallels: normalizedData.parallels,
150-
lastSaved: Date.now(),
151-
isDeployed: workflowData.isDeployed || false,
152-
deployedAt: workflowData.deployedAt,
132+
// Construct response object with workflow data and state from normalized tables
133+
const finalWorkflowData = {
134+
...workflowData,
135+
state: {
136+
// Default values for expected properties
137+
deploymentStatuses: {},
138+
hasActiveWebhook: false,
139+
// Data from normalized tables
140+
blocks: normalizedData.blocks,
141+
edges: normalizedData.edges,
142+
loops: normalizedData.loops,
143+
parallels: normalizedData.parallels,
144+
lastSaved: Date.now(),
145+
isDeployed: workflowData.isDeployed || false,
146+
deployedAt: workflowData.deployedAt,
147+
},
153148
}
154-
logger.info(`[${requestId}] Loaded workflow ${workflowId} from normalized tables`)
155-
} else {
156-
// Fallback to JSON blob
157-
logger.info(
158-
`[${requestId}] Using JSON blob for workflow ${workflowId} - no normalized data found`
159-
)
160-
}
161149

162-
const elapsed = Date.now() - startTime
163-
logger.info(`[${requestId}] Successfully fetched workflow ${workflowId} in ${elapsed}ms`)
150+
logger.info(`[${requestId}] Loaded workflow ${workflowId} from normalized tables`)
151+
const elapsed = Date.now() - startTime
152+
logger.info(`[${requestId}] Successfully fetched workflow ${workflowId} in ${elapsed}ms`)
164153

165-
return NextResponse.json({ data: finalWorkflowData }, { status: 200 })
154+
return NextResponse.json({ data: finalWorkflowData }, { status: 200 })
155+
}
156+
return NextResponse.json({ error: 'Workflow has no normalized data' }, { status: 400 })
166157
} catch (error: any) {
167158
const elapsed = Date.now() - startTime
168159
logger.error(`[${requestId}] Error fetching workflow ${workflowId} after ${elapsed}ms`, error)

apps/sim/app/api/workflows/[id]/state/route.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,6 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
220220
.set({
221221
lastSynced: new Date(),
222222
updatedAt: new Date(),
223-
state: saveResult.jsonBlob, // Also update JSON blob for backward compatibility
224223
})
225224
.where(eq(workflow.id, workflowId))
226225

apps/sim/app/api/workflows/[id]/yaml/route.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,12 @@ import { db } from '@/db'
1818
import { workflowCheckpoints, workflow as workflowTable } from '@/db/schema'
1919
import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/workflow/utils'
2020

21-
// Sim Agent API configuration
2221
const SIM_AGENT_API_URL = env.SIM_AGENT_API_URL || SIM_AGENT_API_URL_DEFAULT
2322

2423
export const dynamic = 'force-dynamic'
2524

2625
const logger = createLogger('WorkflowYamlAPI')
2726

28-
// Request schema for YAML workflow operations
2927
const YamlWorkflowRequestSchema = z.object({
3028
yamlContent: z.string().min(1, 'YAML content is required'),
3129
description: z.string().optional(),
@@ -647,14 +645,13 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
647645
.set({
648646
lastSynced: new Date(),
649647
updatedAt: new Date(),
650-
state: saveResult.jsonBlob,
651648
})
652649
.where(eq(workflowTable.id, workflowId))
653650

654651
// Notify socket server for real-time collaboration (for copilot and editor)
655652
if (source === 'copilot' || source === 'editor') {
656653
try {
657-
const socketUrl = process.env.SOCKET_URL || 'http://localhost:3002'
654+
const socketUrl = env.SOCKET_SERVER_URL || 'http://localhost:3002'
658655
await fetch(`${socketUrl}/api/copilot-workflow-edit`, {
659656
method: 'POST',
660657
headers: { 'Content-Type': 'application/json' },

apps/sim/app/api/workflows/route.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,6 @@ export async function POST(req: NextRequest) {
151151
folderId: folderId || null,
152152
name,
153153
description,
154-
state: initialState,
155154
color,
156155
lastSynced: now,
157156
createdAt: now,

apps/sim/app/api/workflows/yaml/export/route.ts

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -85,14 +85,10 @@ export async function GET(request: NextRequest) {
8585
edgesCount: normalizedData.edges.length,
8686
})
8787

88-
// Use normalized table data - reconstruct complete state object
89-
const existingState =
90-
workflowData.state && typeof workflowData.state === 'object' ? workflowData.state : {}
91-
88+
// Use normalized table data - construct state from normalized tables
9289
workflowState = {
9390
deploymentStatuses: {},
9491
hasActiveWebhook: false,
95-
...existingState,
9692
blocks: normalizedData.blocks,
9793
edges: normalizedData.edges,
9894
loops: normalizedData.loops,
@@ -116,33 +112,10 @@ export async function GET(request: NextRequest) {
116112

117113
logger.info(`[${requestId}] Loaded workflow ${workflowId} from normalized tables`)
118114
} else {
119-
// Fallback to JSON blob
120-
logger.info(
121-
`[${requestId}] Using JSON blob for workflow ${workflowId} - no normalized data found`
115+
return NextResponse.json(
116+
{ success: false, error: 'Workflow has no normalized data' },
117+
{ status: 400 }
122118
)
123-
124-
if (!workflowData.state || typeof workflowData.state !== 'object') {
125-
return NextResponse.json(
126-
{ success: false, error: 'Workflow has no valid state data' },
127-
{ status: 400 }
128-
)
129-
}
130-
131-
workflowState = workflowData.state as any
132-
133-
// Extract subblock values from JSON blob state
134-
if (workflowState.blocks) {
135-
Object.entries(workflowState.blocks).forEach(([blockId, block]: [string, any]) => {
136-
subBlockValues[blockId] = {}
137-
if (block.subBlocks) {
138-
Object.entries(block.subBlocks).forEach(([subBlockId, subBlock]: [string, any]) => {
139-
if (subBlock && typeof subBlock === 'object' && 'value' in subBlock) {
140-
subBlockValues[blockId][subBlockId] = subBlock.value
141-
}
142-
})
143-
}
144-
})
145-
}
146119
}
147120

148121
// Gather block registry and utilities for sim-agent

0 commit comments

Comments
 (0)