Elyra
Elyra The coding agent eTerm The terminal that knows where each command ends Huskarl AI colleagues on your own Mac Starf An activity monitor for Apple silicon that never invents a number etrans An SSH and SFTP client for macOS Elyra VM Virtual machines for macOS, Linux and Windows on your Mac Litr A small, native web browser for macOS Notr A notebook for macOS e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Refr Local-first PDF workspace for macOS Elyra Workspace A desktop workspace for coding agents Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Elyra
Server API reference (PHP)

Server API reference (PHP)

Namespace: Elyra\DataGrid\Server. Install via composer require elyra/datagrid-server.

Grid

Factory entry point.

Grid::table(string $table): GridDefinition

GridDefinition

Fluent builder + the security allow-list.

Method Returns Purpose
table(string) (static) self Create a definition.
key(string $column) self Primary key (tiebreaker, editing, selection).
connection(?string $name) self config/database.php connection.
driver(string|GridDriver) self 'elyrasql', 'mysql', 'pgsql', 'clickhouse', 'sqlite' / 'sqlanywhere', or an instance. Optional: without it the dialect follows the connection (pgsql, sqlite, clickhouse map to themselves; mysql/mariadb/other stay elyrasql).
column(string $key, ...) self Declare one column (see below).
columns(array $keys) self Quick-add columns with defaults.
searchable(array $keys) self Mark columns for global search.
respond(GridRequest) array Execute a query → GridResponse array.
mutate(GridMutation) array create/update/delete → GridMutationResult array.
export(GridRequest, string $format = 'csv', string $filename = 'export') StreamedResponse Streamed CSV/XLSX.
authorize(callable $cb) self Gate create/update/delete/export (fn($ability, $ctx): bool). Required before any write/export unless withoutAuthorization() is called.
withoutAuthorization() self Explicitly opt out of the grid-level write/export gate (route/policy guards it instead).
limits(GridLimits) self Override request guard rails (take/skip/filter-depth/in-values/export-rows/paste-rows).
ask(string $query, ?callable $engine = null) FilterGroup Natural-language → validated filter (see below).
interpret(string $query, ?callable $engine = null) AskResult Same translation, plus what the allow-list dropped.
explain(?FilterGroup, ?string $search, array $headers, array $phrases) string Describe a filter as a sentence (no LLM).
editableColumns() GridColumn[] Columns writable via mutations.
all() array All declared columns.

column() signature

public function column(
    string $key,
    ?string $column = null,          // real column (default: key)
    ?string $expression = null,      // raw SQL (read-only, not quoted)
    bool $sortable = true,
    bool $filterable = true,
    bool $searchable = false,
    ?bool $aggregatable = null,      // numeric → true
    string $type = 'string',         // string|number|date|bool
    bool $editable = false,          // write allow-list
    string $editor = 'text',         // text|number|select|date|checkbox
    string|array|null $rules = null, // Laravel validation
    array $options = [],             // [['value'=>..,'label'=>..]] for select
): self

GridColumn

Value object describing one column (key, column/expression, sortable, filterable, searchable, aggregatable, type, editable, editor, rules, options). isExpression() reports whether it is a raw expression (read-only).

Drivers — Contracts\GridDriver

Implement to support another dialect. Built-in:

Class Notes
Drivers\ElyraSqlDriver (default) FACET() single-pass facets, PERCENTILE/MEDIAN, HYBRID search.
Drivers\MySqlDriver Portability fallback; facets via N GROUP BY; MATCH full-text; no percentile/hybrid.
Drivers\PostgresDriver Double-quoted identifiers; percentile_cont/median; tsvector full-text; no hybrid.
Drivers\ClickHouseDriver OLAP column store; quantile/median; no ESCAPE clause; no hybrid.
Drivers\SqliteDriver SQLite / SQL Anywhere; additive aggregates only; no percentile/hybrid.

Interface surface: name(), quote(), ref(), aggregateExpr(), groupBy(), facetQueries(), searchPredicate().

Engine internals

  • GridEngine — orchestrates respond(): WHERE (filter + search) → viewport rows / groups / total / aggregates / facets / timing. Grouping runs one GROUP BY per level so non-additive aggregates (avg/median/percentile/ countDistinct) are correct at every level. exportKeyset() returns the single-pass export plan (selects / table / where / bindings / key).
  • GridMutator — validates and writes create/update/delete (only editable columns, bound values).
  • GridExporter — streams CSV (BOM + ;) or a rich XLSX (openspout: bold header band, #,##0.00 numeric columns, column widths). Reads in bounded key-range windows (key >= a AND key < b) for constant memory on ElyraSQL.

Natural-language filtering — ask()

ask() translates a plain-language query into a validated FilterGroup using an LLM whose JSON-schema output is restricted to this grid's declared, filterable columns and the known operator set. The result is re-validated against the allow-list (unknown fields/operators dropped) and compiled through the ordinary FilterCompiler, so it can only ever produce filters the grid already permits; no row data is sent to the model. Requires the optional laravel/ai package and a provider key (otherwise a GridException is thrown). The LLM call sits behind an injectable $engine so it can be stubbed in tests.

Each column is offered only the operators its type accepts (read through FilterCompiler::operatorAllowedForType(), so there is no second table to drift), and returned values are checked against the column type. Off-type output is dropped during validation rather than reaching the compiler, which would refuse the whole request.

interpret() returns an AskResult — the surviving FilterGroup plus a dropped list of ['field', 'op', 'reason'], with matched() and partial() helpers. Silently discarding a condition leaves the user with a filter answering a narrower question than they asked; reporting it is what lets a client say so.

explain() runs the other direction — a filter tree rendered as a sentence — and is not an LLM call. A filter tree is structured data, so describing it is formatting rather than inference: faithful by construction, free, and available whether or not the assistant is configured.

Full guide: Natural-language filtering.

Change auditing — GridRowChanged

Applied mutations fire Elyra\DataGrid\Server\Events\GridRowChanged, carrying the row before and after over the declared columns only. changes() gives field => ['old', 'new'] for what actually differs; inverse() gives the mutation that would undo it (null for a delete — the write allow-list cannot restore non-editable columns, and a partial restore would be worse than none).

Capturing the previous row costs one SELECT, so it happens only when something is listening — datagrid.audit.enabled (null = auto, true/false to force). Full guide: Editing.

Live updates — versionColumn()

->versionColumn('updated_at') declares what stamps the grid's data version. meta: ['version'] then answers with dataVersion, built from COUNT(*) and MAX(column) over the request's own filter and search.

The count is what makes a DELETE visible — MAX alone is unchanged by one. It is still a heuristic: a delete and an insert between two polls landing on the same maximum give the same stamp, and a write that does not touch the column is invisible. Full guide: Live updates.

Request limits — GridLimits

GridDefinition::limits() (or config('datagrid.limits')) bounds a single request: maxTake (1000), maxSkip (100k, caps deep OFFSET), maxFilterDepth (15), maxInValues (1000), maxPasteRows (500, enforced in mutateBatch() so every caller is covered — a Livewire paste, your own HTTP endpoint, or application code), maxFacetValues (1000, also the default when a facet request omits top), and maxExportRows (null = unlimited). Exceeding a limit throws a GridException; maxTake and facet top are clamped instead.

maxSkip is neither clamped nor thrown. A window that starts past it returns no rows and a pagingLimit field carrying the limit, so a client can say the limit was reached. Up to 0.9.0 it was clamped, which answered a request for row 150,000 with the rows at 100,000 while the response still said skip: 150000 — the wrong rows, shown in the wrong place. The Livewire pager also stops at the last page the limit allows. Raise it with limits() if your result sets are larger; each deeper page costs a larger OFFSET.

Request breadth is bounded too, because depth limits alone leave a shallow-but-enormous request as a DoS lever on any grid with anonymous read access: maxFilterConditions (200, across the whole tree), maxGroupLevels (5 — the engine runs one query per level), maxAggregates (25 per aggregate list), maxFacets (25, each its own GROUP BY) and maxSearchLength (200 characters).

maxGroupRows (10 000) is different in kind: it bounds a data-dependent result rather than the request. Grouping a column with a million distinct values is not abusive, just expensive, so it clamps each grouping level instead of throwing — and the response carries groupsTruncated: true so a partial grouping is never presented as the whole picture. Render that as a notice; do not ignore it.

maxExportRows is only optional for grids that declare a key. A keyless grid cannot be paginated (no stable order to page by), so its export buffers the whole result set in memory — with no cap that is an OOM waiting to happen, and export() refuses rather than risking it. Declare a key, or set an explicit cap.

Column capabilities

The declared flags are the read allow-list, and each is enforced where it applies: sortable in the sort compiler, filterable in the filter compiler, aggregatable for aggregate expressions, and editable for writes.

filterable: false also blocks facets and grouping on that column. Both emit the column's distinct values, which is exactly what the flag withholds — a facet on a non-filterable column would otherwise dump every value in it.

Exceptions

Exceptions\GridException is thrown for unknown fields, disallowed operations (non-sortable/filterable/aggregatable), unsupported driver features, bad operators, exceeded request limits, an unauthorized mutation/export, or an unavailable assistant — the security guardrail that keeps clients within the declared schema.

Service provider

GridServiceProvider is auto-discovered. The Livewire package adds GridLivewireServiceProvider (views, translations, inlined theme).