({ workspaceId, tableId }: RowMutationContext)
| 1107 | * restores rows on failure/cancel). |
| 1108 | */ |
| 1109 | export function useDeleteTableRowsAsync({ workspaceId, tableId }: RowMutationContext) { |
| 1110 | const queryClient = useQueryClient() |
| 1111 | |
| 1112 | return useMutation({ |
| 1113 | mutationFn: async ({ |
| 1114 | filter, |
| 1115 | excludeRowIds, |
| 1116 | estimatedCount, |
| 1117 | }: DeleteTableRowsAsyncVariables) => { |
| 1118 | return requestJson(deleteTableRowsAsyncContract, { |
| 1119 | params: { tableId }, |
| 1120 | body: { workspaceId, filter, excludeRowIds, estimatedCount }, |
| 1121 | }) |
| 1122 | }, |
| 1123 | onMutate: async ({ filter, sort, excludeRowIds, estimatedCount }) => { |
| 1124 | // Target the exact infinite-rows query for the view the user is on — not every cached view. |
| 1125 | const activeKey = tableKeys.infiniteRows( |
| 1126 | tableId, |
| 1127 | tableRowsParamsKey({ pageSize: TABLE_LIMITS.MAX_QUERY_LIMIT, filter: filter ?? null, sort }) |
| 1128 | ) |
| 1129 | await queryClient.cancelQueries({ queryKey: activeKey }) |
| 1130 | const previousRows = |
| 1131 | queryClient.getQueryData<InfiniteData<TableRowsResponse, TableRowsPageParam>>(activeKey) |
| 1132 | const previousDetail = queryClient.getQueryData<TableDefinition>(tableKeys.detail(tableId)) |
| 1133 | const keep = new Set(excludeRowIds ?? []) |
| 1134 | // The active view's post-delete total is exactly the kept (deselected) rows — every other |
| 1135 | // matching row is doomed. Without this the footer / select-all label stays at the old total |
| 1136 | // until the job's terminal refetch. |
| 1137 | queryClient.setQueryData<InfiniteData<TableRowsResponse, TableRowsPageParam>>( |
| 1138 | activeKey, |
| 1139 | (old) => |
| 1140 | old |
| 1141 | ? { |
| 1142 | ...old, |
| 1143 | pages: old.pages.map((page) => ({ |
| 1144 | ...page, |
| 1145 | rows: page.rows.filter((r) => keep.has(r.id)), |
| 1146 | ...(page.totalCount != null ? { totalCount: keep.size } : {}), |
| 1147 | })), |
| 1148 | } |
| 1149 | : old |
| 1150 | ) |
| 1151 | if (estimatedCount != null) { |
| 1152 | queryClient.setQueryData<TableDefinition>(tableKeys.detail(tableId), (p) => |
| 1153 | p ? { ...p, rowCount: Math.max(0, p.rowCount - estimatedCount) } : p |
| 1154 | ) |
| 1155 | } |
| 1156 | return { activeKey, previousRows, previousDetail } |
| 1157 | }, |
| 1158 | onSuccess: ({ data }) => { |
| 1159 | // Lock the SSE job consumer onto this run so its running/terminal events are accepted, and |
| 1160 | // flip the list-driven tray into "deleting" without waiting for a poll. |
| 1161 | queryClient.setQueryData<TableDefinition>(tableKeys.detail(tableId), (p) => |
| 1162 | p ? { ...p, jobStatus: 'running', jobId: data.jobId, jobType: 'delete' } : p |
| 1163 | ) |
| 1164 | queryClient.invalidateQueries({ queryKey: tableKeys.lists() }) |
| 1165 | }, |
| 1166 | onError: (error, _vars, context) => { |
no test coverage detected