@@ -8,19 +8,47 @@ import type { Workflow, GraphDefinition } from '../../../src/workflow/types/work
88
99function getFileStorage ( ) { return getFileStorageInstance ( ) }
1010
11+ /**
12+ * Ensure a workflow name is unique across all workflows.
13+ * If a collision is found (excluding the workflow with `excludeId`),
14+ * appends (2), (3), etc. until unique.
15+ */
16+ function ensureUniqueName ( db : ReturnType < typeof getDatabase > , name : string , excludeId : string | null ) : string {
17+ const trimmed = name . trim ( ) || 'Untitled Workflow'
18+ const existingNames = new Set < string > ( )
19+ const result = excludeId
20+ ? db . exec ( 'SELECT name FROM workflows WHERE id != ?' , [ excludeId ] )
21+ : db . exec ( 'SELECT name FROM workflows' )
22+ if ( result . length > 0 ) {
23+ for ( const row of result [ 0 ] . values ) {
24+ existingNames . add ( row [ 0 ] as string )
25+ }
26+ }
27+
28+ if ( ! existingNames . has ( trimmed ) ) return trimmed
29+
30+ let counter = 2
31+ while ( existingNames . has ( `${ trimmed } (${ counter } )` ) ) counter ++
32+ return `${ trimmed } (${ counter } )`
33+ }
34+
1135export function createWorkflow ( name : string ) : Workflow {
1236 const db = getDatabase ( )
1337 const id = uuid ( )
1438 const now = new Date ( ) . toISOString ( )
1539 const graphDef : GraphDefinition = { nodes : [ ] , edges : [ ] }
40+
41+ // Ensure unique name — append (2), (3), etc. if needed
42+ const finalName = ensureUniqueName ( db , name , null )
43+
1644 db . run (
1745 `INSERT INTO workflows (id, name, created_at, updated_at, graph_definition, status) VALUES (?, ?, ?, ?, ?, ?)` ,
18- [ id , name , now , now , JSON . stringify ( graphDef ) , 'draft' ]
46+ [ id , finalName , now , now , JSON . stringify ( graphDef ) , 'draft' ]
1947 )
2048 persistDatabase ( )
2149 // Also create the workflow directory and initial snapshot on disk
22- getFileStorage ( ) . saveWorkflowSnapshot ( id , name , graphDef )
23- return { id, name, createdAt : now , updatedAt : now , graphDefinition : graphDef , status : 'draft' }
50+ getFileStorage ( ) . saveWorkflowSnapshot ( id , finalName , graphDef )
51+ return { id, name : finalName , createdAt : now , updatedAt : now , graphDefinition : graphDef , status : 'draft' }
2452}
2553
2654export function getWorkflowById ( id : string ) : Workflow | null {
@@ -54,12 +82,20 @@ export function listWorkflows(): Array<{ id: string; name: string; createdAt: st
5482export function updateWorkflow ( id : string , name : string , graphDefinition : GraphDefinition , status ?: Workflow [ 'status' ] ) : void {
5583 const db = getDatabase ( )
5684 const now = new Date ( ) . toISOString ( )
85+
86+ // Get old name to detect rename
87+ const existing = getWorkflowById ( id )
88+ const oldName = existing ?. name ?? name
89+
90+ // Ensure unique name if it changed
91+ const finalName = ( name !== oldName ) ? ensureUniqueName ( db , name , id ) : name
92+
5793 if ( status ) {
5894 db . run ( 'UPDATE workflows SET name = ?, graph_definition = ?, updated_at = ?, status = ? WHERE id = ?' ,
59- [ name , JSON . stringify ( graphDefinition ) , now , status , id ] )
95+ [ finalName , JSON . stringify ( graphDefinition ) , now , status , id ] )
6096 } else {
6197 db . run ( 'UPDATE workflows SET name = ?, graph_definition = ?, updated_at = ? WHERE id = ?' ,
62- [ name , JSON . stringify ( graphDefinition ) , now , id ] )
98+ [ finalName , JSON . stringify ( graphDefinition ) , now , id ] )
6399 }
64100 // Preserve existing currentOutputId for nodes that already have execution results
65101 const existingOutputIds = new Map < string , string | null > ( )
@@ -100,19 +136,36 @@ export function updateWorkflow(id: string, name: string, graphDefinition: GraphD
100136 db . run ( 'PRAGMA foreign_keys = ON' )
101137 }
102138 persistDatabase ( )
103- getFileStorage ( ) . saveWorkflowSnapshot ( id , name , graphDefinition )
139+
140+ // Rename directory on disk if name changed
141+ if ( finalName !== oldName ) {
142+ getFileStorage ( ) . renameWorkflowDir ( id , oldName , finalName )
143+ }
144+ getFileStorage ( ) . saveWorkflowSnapshot ( id , finalName , graphDefinition )
104145}
105146
106147export function renameWorkflow ( id : string , newName : string ) : void {
107148 const db = getDatabase ( )
108149 const now = new Date ( ) . toISOString ( )
109- db . run ( 'UPDATE workflows SET name = ?, updated_at = ? WHERE id = ?' , [ newName , now , id ] )
110- persistDatabase ( )
111- // Update file storage name mapping and snapshot
150+
151+ // Get old name before rename (needed for directory rename)
112152 const existing = getWorkflowById ( id )
113- if ( existing ) {
114- getFileStorage ( ) . saveWorkflowSnapshot ( id , newName , existing . graphDefinition )
115- }
153+ if ( ! existing ) return
154+
155+ const oldName = existing . name
156+
157+ // Ensure unique name — append (2), (3), etc. if collides with another workflow
158+ const finalName = ensureUniqueName ( db , newName , id )
159+
160+ db . run ( 'UPDATE workflows SET name = ?, updated_at = ? WHERE id = ?' , [ finalName , now , id ] )
161+ persistDatabase ( )
162+
163+ // Rename the data directory on disk (handles collision cleanup)
164+ const fs = getFileStorage ( )
165+ fs . renameWorkflowDir ( id , oldName , finalName )
166+
167+ // Update the snapshot file with the new name
168+ fs . saveWorkflowSnapshot ( id , finalName , existing . graphDefinition )
116169}
117170
118171export function deleteWorkflow ( id : string ) : void {
0 commit comments