<!-- source-hash: 4c242f0aea85fea9fe359656cea3e3d5 -->
A thin wrapper around TanStack's `useReactTable` with sensible defaults for server-driven (Relay/REST) pagination, sorting, and filtering. Row-model factories are instantiated once at module load to preserve internal TanStack caches across renders.

## Key Components

- **`UseDataTableOptions<T>`** — Extends TanStack's `TableOptions<T>` (omitting `getCoreRowModel`) with two additional flags:
  - `clientSideSorting` — enables `getSortedRowModel` (default: `false`)
  - `clientSideFiltering` — enables `getFilteredRowModel` (default: `false`)
- **`useDataTable<T>`** — The main hook. Wraps `useReactTable` and automatically sets `manualSorting`/`manualFiltering` to the inverse of the client-side flags unless explicitly overridden.

## Usage Example

```typescript
import { useMemo, useCallback } from 'react'
import { useDataTable } from './use-data-table'
import type { ColumnDef } from '@tanstack/react-table'

// Stable references — required to avoid row-model rebuilds on every render
const data = useMemo(() => rows.map(toUiRow), [rows])
const columns = useMemo<ColumnDef<MyRow>[]>(() => [
  { accessorKey: 'name', header: 'Name' },
  { accessorKey: 'status', header: 'Status' },
], [])
const getRowId = useCallback((row: MyRow) => row.id, [])
const sorting = useMemo(() => [{ id: sortKey, desc: sortDesc }], [sortKey, sortDesc])

const table = useDataTable({
  data,
  columns,
  getRowId,
  state: { sorting },          // wrapper object can be inline
  onSortingChange: handleSort, // callbacks can be inline
  // clientSideSorting: true,  // opt-in for fully client-side tables
})
```

## Reference-Stability Notes

| Option | Safe inline? |
|---|---|
| `data`, `columns` | ❌ — must `useMemo` |
| `state` (wrapper) | ✅ — build inline freely |
| `state.sorting`, `state.pagination` | ❌ — must be stable refs |
| `onXChange` callbacks | ✅ — invoked, never compared |
| `meta`, `defaultColumn` | ⚠️ — recommended `useMemo` |