@@ -10,7 +10,6 @@ import { parseRequest } from '@/lib/api/server'
1010import { asOrchestrationError } from '@/lib/core/orchestration/types'
1111import { generateRequestId } from '@/lib/core/utils/request'
1212import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
13- import { withFolderTreeLock } from '@/lib/folders/locks'
1413import { loadActiveFolderPathIndex } from '@/lib/folders/queries'
1514import { getTableById } from '@/lib/table'
1615import { signalTableSchemaChanged } from '@/lib/table/events'
@@ -21,7 +20,7 @@ import {
2120} from '@/lib/table/orchestration'
2221import { checkAccess } from '@/app/api/table/utils'
2322import { checkRateLimit , resolveWorkspaceScope } from '@/app/api/v1/middleware'
24- import { folderPathForId , resolveFolderPathId } from '@/app/api/v2/lib/folders'
23+ import { folderPathForId , resolveFolderPathIdentity } from '@/app/api/v2/lib/folders'
2524import { v2ApiGateError } from '@/app/api/v2/lib/gate'
2625import {
2726 v2Data ,
@@ -155,75 +154,66 @@ export const PATCH = withRouteHandler(async (request: NextRequest, context: Tabl
155154 return v2Error ( 'NOT_FOUND' , 'Table not found' )
156155 }
157156
158- return await withFolderTreeLock ( table . workspaceId , 'table' , async ( tx ) => {
159- const folderIndex = await loadActiveFolderPathIndex ( table . workspaceId , 'table' , tx )
160- const folderId =
161- validated . folderPath === undefined
162- ? undefined
163- : resolveFolderPathId ( folderIndex , validated . folderPath )
164- if ( validated . folderPath !== undefined && folderId === undefined ) {
165- return v2Error ( 'NOT_FOUND' , 'Folder not found in this workspace' )
166- }
157+ const resolution =
158+ validated . folderPath === undefined
159+ ? undefined
160+ : await resolveFolderPathIdentity ( {
161+ workspaceId : table . workspaceId ,
162+ resourceType : 'table' ,
163+ path : validated . folderPath ,
164+ } )
165+ if ( resolution && ! resolution . found ) {
166+ return v2Error ( 'NOT_FOUND' , 'Folder not found in this workspace' )
167+ }
167168
168- // Rename and move retain their shared services' independent transactions and audits.
169- // Validate deterministic failures first and report `applied` so callers can reconcile
170- // if a later fault lands after an earlier operation commits.
171- let failure : { outcome : OrchestrationOutcome ; fallback : string } | null = null
172-
173- if ( validated . name !== undefined ) {
174- const outcome = await performRenameTable ( {
175- table,
176- newName : validated . name ,
177- userId,
178- requestId,
179- request,
180- } )
181- if ( outcome . success ) applied . push ( 'name' )
182- else failure = { outcome, fallback : 'Failed to rename table' }
183- }
169+ let failure : { outcome : OrchestrationOutcome ; fallback : string } | null = null
170+
171+ if ( validated . name !== undefined ) {
172+ const outcome = await performRenameTable ( {
173+ table,
174+ newName : validated . name ,
175+ userId,
176+ requestId,
177+ request,
178+ } )
179+ if ( outcome . success ) applied . push ( 'name' )
180+ else failure = { outcome, fallback : 'Failed to rename table' }
181+ }
184182
185- if ( ! failure && validated . folderPath !== undefined ) {
186- const outcome = await performMoveTableToFolder ( {
187- table,
188- folderId : folderId ?? null ,
189- userId,
190- requestId,
191- request,
192- } )
193- if ( outcome . success ) {
194- applied . push ( 'folderPath' )
195- } else {
196- // The move re-asserts workspace and active state, so a miss means the
197- // table was archived between `checkAccess` and the write.
198- failure = {
199- outcome :
200- outcome . errorCode === 'not_found'
201- ? { ...outcome , error : 'Table not found' }
202- : outcome ,
203- fallback : 'Failed to move table' ,
204- }
183+ if ( ! failure && validated . folderPath !== undefined ) {
184+ const outcome = await performMoveTableToFolder ( {
185+ table,
186+ folderId : resolution ?. folderId ?? null ,
187+ userId,
188+ requestId,
189+ request,
190+ } )
191+ if ( outcome . success ) {
192+ applied . push ( 'folderPath' )
193+ } else {
194+ failure = {
195+ outcome :
196+ outcome . errorCode === 'not_found' ? { ...outcome , error : 'Table not found' } : outcome ,
197+ fallback : 'Failed to move table' ,
205198 }
206199 }
200+ }
207201
208- // Live-collab: tell open viewers the definition changed so they refetch.
209- if ( applied . length > 0 ) signalTableSchemaChanged ( tableId )
210- if ( failure ) {
211- return v2TableOrchestrationError ( failure . outcome , failure . fallback , appliedDetails ( applied ) )
212- }
202+ if ( applied . length > 0 ) signalTableSchemaChanged ( tableId )
203+ if ( failure ) {
204+ return v2TableOrchestrationError ( failure . outcome , failure . fallback , appliedDetails ( applied ) )
205+ }
213206
214- // Re-read so the response reflects every applied change at once. A miss
215- // means the table was archived after the writes committed, so the caller
216- // still has to be told what landed.
217- const updated = await getTableById ( tableId )
218- if ( ! updated ) {
219- return v2Error ( 'NOT_FOUND' , 'Table not found' , { details : appliedDetails ( applied ) } )
220- }
207+ const updated = await getTableById ( tableId )
208+ if ( ! updated ) {
209+ return v2Error ( 'NOT_FOUND' , 'Table not found' , { details : appliedDetails ( applied ) } )
210+ }
221211
222- return v2Data (
223- { table : toApiTable ( updated , folderPathForId ( folderIndex , updated . folderId ) ) } ,
224- { rateLimit }
225- )
226- } )
212+ const folderIndex = await loadActiveFolderPathIndex ( table . workspaceId , 'table' )
213+ return v2Data (
214+ { table : toApiTable ( updated , folderPathForId ( folderIndex , updated . folderId ) ) } ,
215+ { rateLimit }
216+ )
227217 } catch ( error ) {
228218 const details = appliedDetails ( applied )
229219
0 commit comments