-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Expand file tree
/
Copy pathsave.ts
More file actions
119 lines (105 loc) · 3.86 KB
/
Copy pathsave.ts
File metadata and controls
119 lines (105 loc) · 3.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import { db, workflowBlocks, workflowEdges, workflowSubflows } from '@sim/db'
import { createLogger } from '@sim/logger'
import { toError } from '@sim/utils/errors'
import type { BlockState, WorkflowState } from '@sim/workflow-types/workflow'
import {
normalizeWorkflowEdgeSourceHandle,
normalizeWorkflowEdgeTargetHandle,
SUBFLOW_TYPES,
} from '@sim/workflow-types/workflow'
import type { InferInsertModel } from 'drizzle-orm'
import { eq } from 'drizzle-orm'
import { generateLoopBlocks, generateParallelBlocks } from './subflow-helpers'
import type { DbOrTx } from './types'
const logger = createLogger('WorkflowPersistenceSave')
type SubflowInsert = InferInsertModel<typeof workflowSubflows>
export async function saveWorkflowToNormalizedTables(
workflowId: string,
state: WorkflowState,
externalTx?: DbOrTx
): Promise<{ success: boolean; error?: string }> {
const blockRecords = state.blocks as Record<string, BlockState>
const canonicalLoops = generateLoopBlocks(blockRecords)
const canonicalParallels = generateParallelBlocks(blockRecords)
const execute = async (tx: DbOrTx) => {
await Promise.all([
tx.delete(workflowBlocks).where(eq(workflowBlocks.workflowId, workflowId)),
tx.delete(workflowEdges).where(eq(workflowEdges.workflowId, workflowId)),
tx.delete(workflowSubflows).where(eq(workflowSubflows.workflowId, workflowId)),
])
if (Object.keys(state.blocks).length > 0) {
const blockInserts = Object.values(state.blocks).map((block) => ({
id: block.id,
workflowId,
type: block.type,
name: block.name || '',
positionX: String(block.position?.x || 0),
positionY: String(block.position?.y || 0),
enabled: block.enabled ?? true,
horizontalHandles: block.horizontalHandles ?? true,
advancedMode: block.advancedMode ?? false,
triggerMode: block.triggerMode ?? false,
height: String(block.height || 0),
subBlocks: block.subBlocks || {},
outputs: block.outputs || {},
errorEnabled: block.errorEnabled ?? false,
/**
* Persisted verbatim, including a disabled policy, so the configured
* numbers survive switching retry off and back on. Whether it runs is
* decided by `resolveBlockRetryConfig` at execution time.
*/
retry: block.retry ?? null,
data: block.data || {},
parentId: block.data?.parentId || null,
extent: block.data?.extent || null,
locked: block.locked ?? false,
}))
await tx.insert(workflowBlocks).values(blockInserts)
}
if (state.edges.length > 0) {
const edgeInserts = state.edges.map((edge) => ({
id: edge.id,
workflowId,
sourceBlockId: edge.source,
targetBlockId: edge.target,
sourceHandle: normalizeWorkflowEdgeSourceHandle(edge.sourceHandle),
targetHandle: normalizeWorkflowEdgeTargetHandle(edge.targetHandle),
}))
await tx.insert(workflowEdges).values(edgeInserts)
}
const subflowInserts: SubflowInsert[] = []
Object.values(canonicalLoops).forEach((loop) => {
subflowInserts.push({
id: loop.id,
workflowId,
type: SUBFLOW_TYPES.LOOP,
config: loop,
})
})
Object.values(canonicalParallels).forEach((parallel) => {
subflowInserts.push({
id: parallel.id,
workflowId,
type: SUBFLOW_TYPES.PARALLEL,
config: parallel,
})
})
if (subflowInserts.length > 0) {
await tx.insert(workflowSubflows).values(subflowInserts)
}
}
if (externalTx) {
await execute(externalTx)
return { success: true }
}
try {
await db.transaction(execute)
return { success: true }
} catch (error) {
logger.error(`Error saving workflow ${workflowId} to normalized tables:`, error)
return {
success: false,
error: toError(error).message,
}
}
}