| 453 | } |
| 454 | |
| 455 | export function tableRowsInfiniteOptions({ |
| 456 | workspaceId, |
| 457 | tableId, |
| 458 | pageSize, |
| 459 | filter, |
| 460 | sort, |
| 461 | }: Omit<InfiniteTableRowsParams, 'enabled'>) { |
| 462 | const paramsKey = tableRowsParamsKey({ pageSize, filter, sort }) |
| 463 | return infiniteQueryOptions({ |
| 464 | queryKey: tableKeys.infiniteRows(tableId, paramsKey), |
| 465 | queryFn: ({ pageParam, signal }) => { |
| 466 | const param = pageParam as TableRowsPageParam |
| 467 | return fetchTableRows({ |
| 468 | workspaceId, |
| 469 | tableId, |
| 470 | limit: pageSize, |
| 471 | ...(typeof param === 'number' ? { offset: param } : { after: param }), |
| 472 | filter, |
| 473 | sort, |
| 474 | includeTotal: param === 0, |
| 475 | signal, |
| 476 | }) |
| 477 | }, |
| 478 | initialPageParam: 0 as TableRowsPageParam, |
| 479 | getNextPageParam: (lastPage, _allPages, lastPageParam): TableRowsPageParam | undefined => { |
| 480 | if (lastPage.rows.length < pageSize) return undefined |
| 481 | // Default order pages by keyset cursor — each page is an index seek on (order_key, id), |
| 482 | // where OFFSET would re-scan every prior row (O(N²) across a deep scroll / full drain). |
| 483 | // Sorted views (and legacy rows without an order key) fall back to offset paging. |
| 484 | if (!sort) { |
| 485 | const last = lastPage.rows[lastPage.rows.length - 1] |
| 486 | if (last?.orderKey) return { orderKey: last.orderKey, id: last.id } |
| 487 | } |
| 488 | const param = lastPageParam as TableRowsPageParam |
| 489 | return (typeof param === 'number' ? param : 0) + lastPage.rows.length |
| 490 | }, |
| 491 | staleTime: 30 * 1000, |
| 492 | }) |
| 493 | } |
| 494 | |
| 495 | /** Page 0 fetches a server-side `COUNT(*)`; subsequent pages skip it. */ |
| 496 | export function useInfiniteTableRows({ |