import { DataSource } from '@object-ui/types'; /** * useRecordQuery — the shared record-query kernel behind record pickers. * * Encapsulates the query/pagination/search/sort loop that was previously * duplicated (and independently tuned) inside `RecordPickerDialog.fetchRecords` * and `LookupField.fetchLookupData`: * * - builds the `QueryParams` (`$top`/`$skip`/`$search`/`$searchFields`/ * `$orderby`/`$filter`/`$expand`), * - issues `dataSource.find(objectName, params)` and normalises the result * (`{ data, total }`, tolerating a bare array), * - owns `records`/`loading`/`error`/`total` plus the `page`/`search`/`sort` * controls, with a debounced search path, * - clears itself when `enabled` goes false (e.g. a dialog closing). * * The caller keeps ownership of *selection* state and of record→option mapping; * this hook only answers "what records match the current query". It is the * reusable core the search-first PeoplePicker (and a future org-tree tier) * compose on top of. * * Effect shape intentionally mirrors the original components to preserve * behaviour: the fetch effect keys on page/sort/filter/expand (NOT `search`, * which drives its own debounced fetch), and state resets live in a separate * `enabled`-keyed effect so they never cascade into a fetch (React #185). */ export interface UseRecordQueryOptions { /** Backing data source. When absent/invalid the hook stays idle. */ dataSource?: DataSource | null; /** Object/resource name to query (e.g. `sys_user`). */ objectName?: string | null; /** * Gate fetching. When false no request is issued and query state is cleared. * Typically wired to a dialog's `open`, plus any "dependencies satisfied" * guard. Default `true`. */ enabled?: boolean; /** `$top` — page size. Default 50. */ pageSize?: number; /** * When true, use page-based pagination (`$skip = (page - 1) * pageSize`). * When false (default) a single page of `pageSize` records is fetched. */ paginate?: boolean; /** * `$filter` — already merged by the caller (base `lookup_filters`, dependent * lookup chain, candidate hygiene like `banned != true`, …). Compared by * value, so a referentially-new-but-equal object each render will not loop. * * Either the `QueryParams.$filter` record form or a lowered ObjectQL AST node * (an array), since the picker's merge now produces both (#3831). Typed * `unknown` rather than `Record< string, any >` so an array cannot slip * through a type that silently accepts it; emptiness is decided by * {@link hasFilter}, not by `Object.keys`. */ filter?: unknown; /** `$expand` — related entities to include (e.g. `['primary_business_unit_id']`). */ expand?: string[]; /** `$searchFields` — narrow the server searchable set (ADR-0061). */ searchFields?: string[]; /** Debounce applied to {@link UseRecordQueryResult.setSearch}, in ms. Default 300. */ debounceMs?: number; } export interface RecordQuerySort { field: string; direction: 'asc' | 'desc'; } export interface UseRecordQueryResult { /** Records returned by the current query. */ records: any[]; loading: boolean; error: string | null; /** Total matching records (server-reported, else the current page length). */ total: number; /** `ceil(total / pageSize)`, at least 1. */ totalPages: number; page: number; search: string; sort: RecordQuerySort | null; /** Jump to a page (1-indexed). No-op unless `paginate` is set. */ setPage: (page: number) => void; /** Set the search term; debounced, and resets to page 1. */ setSearch: (query: string) => void; /** Toggle sort on a field (new field → asc, same field → flip); resets to page 1. */ toggleSort: (field: string) => void; /** Set the sort directly (or clear with `null`); resets to page 1. */ setSort: (sort: RecordQuerySort | null) => void; /** Clear query state (search/page/sort/records/error). */ reset: () => void; /** Imperatively refetch with the current params. */ refetch: () => void; } export declare function useRecordQuery(options: UseRecordQueryOptions): UseRecordQueryResult;