# Source API

> TableSource, CallbackSource, TableQuery, and TableResult contracts.

Source: https://docs.inertiax.dev/table/reference/source/

Types below live under `InertiaX\Table\Sources`; the source interface is `InertiaX\Table\Sources\Contracts\TableSource`. Start with [Custom sources](https://docs.inertiax.dev/table/guide/custom-sources/) to build a repository adapter or [Connect an API source](https://docs.inertiax.dev/table/guide/api-source/) for HTTP request translation.

## TableSource

```php
interface TableSource
{
    public function fetch(TableQuery $query): TableResult;
}
```

Supply an instance, implementing class name, container-bound interface extending `TableSource`, or deferred closure returning one through `data()`. Source instances are reusable strategies and are not cloned; do not retain request state on a shared instance. Class resolution and options happen before state validation. `fetch()` runs after validation. The explicit [unavailable-page signal](https://docs.inertiax.dev/table/guide/url-recovery/#custom-sources) shows a table-local problem without retrying the source. Existing model class names retain their Eloquent meaning; an ambiguous model/source class fails.

## CallbackSource

`CallbackSource::make(Closure $callback): CallbackSource` creates an immutable definition. The callback returns `TableResult` and selects `TableQuery $query`, `string $tableId`, and explicitly bound services by supported parameter name/type. Optional defaults remain available for ordinary unresolved dependencies. Required unresolved or ambiguous dependencies fail contextually.

A bare `data(fn () => ...)` resolves a source earlier; it cannot request `TableQuery` or return a `TableResult`/paginator directly. Use the wrapper for retrieval.

## TableQuery

`TableQuery` is final and readonly. InertiaX constructs it from validated table state. Its public constructor takes the following named arguments; defaults are useful for adapter tests:

| Property            | Type                                                 | Default / meaning                                           |
| ------------------- | ---------------------------------------------------- | ----------------------------------------------------------- |
| `tableId`           | `string`                                             | Required table ID                                           |
| `rowKey`            | `string`                                             | `id`; declared record identity field                        |
| `paginationEnabled` | `bool`                                               | `true`                                                      |
| `page`              | `int`                                                | `1`; positive, one-based page                               |
| `pageSize`          | `int`                                                | `10`; positive and allowed by the table                     |
| `sorting`           | `list<array{key: string, direction: 'asc'\|'desc'}>` | `[]`; requested precedence, without an implicit tie-breaker |
| `filters`           | Root group                                           | `['operator' => 'and', 'conditions' => []]`                 |
| `search`            | `string`                                             | `''`; trimmed validated term                                |
| `searchableColumns` | `list<string>`                                       | `[]`; declared eligible fields, not arbitrary client fields |

`offset(): int` returns `(page - 1) * pageSize`, or `0` when pagination is disabled. Overflow fails before retrieval. `effectiveSorting(): array` returns requested sorting with one ascending row-key tie-breaker if an active sort omits it. Empty sorting stays empty.

Each filter group is `{ operator: 'and'|'or', conditions: [...] }`; a child is another group or `{ key, clause, value }`. `key` is the filter’s key, and `value` matches the clause’s input: `null` when it takes none. Repeated fields, nested AND/OR groups, order, native scalar types, and range/list values are preserved. Values are detached JSON data. Namespaced custom clauses remain identifiers for the application to translate. Built-in clause meanings should stay consistent. See [Filter groups](https://docs.inertiax.dev/table/reference/filter/#filter-groups) for bounds and basic/advanced modes.

Constructing a query manually does not perform the Table’s full definition/state validation. Sources should still map trusted fields and reject provider limitations before retrieval. Selection, column visibility, PHP callbacks, provider URLs, and credentials are not query fields.

### Search

Only columns with `type('text')` can be searchable with a custom source. A `TextColumn` changed to a custom type cannot be searchable on this path. The source implements matching across `searchableColumns` using `search`; `searchUsing()` callbacks are rejected. Document the provider’s comparison and collation semantics.

## TableResult

| Entry                                                                            | Contract                                                                                      |
| -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `new TableResult(iterable $rows, int $total)`                                    | Snapshot finite raw array/object records into a list; exact nonnegative JavaScript-safe total |
| `TableResult::all(iterable $rows)`                                               | Derive total from all records for unpaginated retrieval                                       |
| `TableResult::fromPaginator(LengthAwarePaginator $paginator, TableQuery $query)` | Require enabled pagination and matching page/page size; use items and total                   |

The public readonly properties are `array $rows` and `int $total`. Record generics preserve array or object types; readonly does not deep-freeze application record objects. Row keys and column cell serialization are still validated by the normal pipeline.

### Result validation

- `total` is the exact number of matching records **before** pagination, not the length of the page.
- Return `min(pageSize, max(0, total - offset))` records. Short pages with a larger claimed total and excess rows fail validation.
- `lastPage` is derived from the total and page size, with a minimum of 1. Empty results require total `0`, no rows, and page `1`. Out-of-range pages fail without clamping or a second fetch.
- With `pagination(false)`, return every matching record and its count. `TableResult::all($rows)` derives that count. A source may reject unpaginated retrieval explicitly.
- Totals must be nonnegative JavaScript-safe integers. Rows must be finite array/object records; missing, duplicate, or invalid identities fail normal row-key validation.

InertiaX does not filter, search, sort, or slice returned rows again. Paginator URLs are ignored; the frontend owns navigation. Unknown totals, simple/cursor paginators, and infinite streams are unsupported. Application code must keep the provider’s page and count consistent.

## Filters and options

Input `modifyUsing()` and `validateUsing()` callbacks run before fetch; their resulting values must remain JSON data. Values follow [Clause validation](https://docs.inertiax.dev/table/reference/clause/#validation-order). For an application comparison, use `Clause::make('acme/in-region')->validateUsing(...)` and translate that key in your source. It needs no SQL callback. A filter’s `column()` does not reach the source: map each filter key to the provider’s field there.

Direct Clause/Filter implementations retain definition and value-validation behavior. Their Eloquent/Collection `apply()` methods are not invoked. Built-in Boolean storage maps are likewise not applied to custom results; sources receive the validated Boolean input.

### Source-specific options

`DataSourceKind::Custom` (under `Table\Enums`) declares custom-source select option support. `options(Closure, DataSourceKind::Custom)` supplies the filter, request, resolved `TableSource` or `CallbackSource`, and bound services. Options precede final state validation; named `baseSource`, `query`, and `state` requests fail with guidance. An options callback may make its own metadata request, so invalid table state does not guarantee zero metadata I/O. There is no faceting contract. Explicit option arrays also work. See the [API source example](https://docs.inertiax.dev/table/guide/api-source/#declare-the-supported-controls).

An options closure typed to a particular source class requires that resolved class. If a per-use `data()` override supplies a `CallbackSource` instead, replace the closure with explicit options or a compatible callback.

## Unsupported configuration

`autoColumns()`, model-derived columns, and select `autoOptions()` require local model/data information and fail for custom sources. Automatic filters derived from explicit columns remain available, without the `is-set` and `is-not-set` clauses that nullable database columns get; advertise only operations the source implements.

`sortUsing()`, `searchUsing()`, `applyClauseUsing()`, and clause `applyUsing()` conflict with custom retrieval even if they declare `DataSourceKind::Custom` support. Put those operations in the source.

## Failures and execution

Invalid table state fails before row fetch. Provider exceptions retain source/table/phase context and their original cause. Laravel authentication, authorization, not-found, HTTP, and validation exceptions preserve their response semantics through both source forms.

Server execution fetches once; refreshing starts a new execution. `TablePageUnavailableException` produces a [table-local problem](https://docs.inertiax.dev/table/guide/url-recovery/) for Inertia URL pagination without an automatic retry. Caches and remote retries belong to application code. Browser cancellation does not guarantee remote cancellation.

See [Callback context](https://docs.inertiax.dev/table/reference/callbacks/) for injectable arguments and resolution rules.
