Elyra
Elyra The coding agent 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 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

Live updates

Poll a cheap version stamp; re-fetch only the visible window when it moves. The grid already fetches O(viewport), so a live grid costs one aggregate query per poll plus one page fetch per actual change.

// Declare what stamps the version. Without this the stamp is a row count, which
// sees inserts and deletes but not in-place edits.
Grid::table('sales')->key('id')
    ->columns(['sku', 'region', 'status'])
    ->column('revenue', type: 'number')
    ->column('updated_at', type: 'date')
    ->versionColumn('updated_at');
class SalesGrid extends DataGrid
{
    public bool $liveUpdates = true;
    public int $livePollSeconds = 5;
}
// Vue / Svelte / headless
createGrid({ columns, fetch, liveUpdates: { intervalMs: 5000 } });

What the stamp is

meta: ['version'] returns dataVersion, built from COUNT(*) and MAX(versionColumn) over the same filter and search as the request.

The count is not decoration. MAX(updated_at) on its own cannot see a DELETE — the maximum is unchanged, so a removed row would sit on screen until something else happened to bump the column. The count catches inserts and deletes; the maximum catches in-place updates.

Change MAX alone COUNT + MAX
in-place update
insert
delete
delete + insert between two polls, same maximum
write that does not touch the version column

So it is a heuristic: a cheap "probably changed" signal, not a correctness guarantee. The worst case is being briefly stale until the next real change, which is the right trade for a grid. If you need exactness, drive revalidate() from a broadcast event instead of polling.

Without ->versionColumn(...) the stamp is just the count: inserts and deletes show up, in-place edits do not.

What it costs

One aggregate query per poll. take: 0 means no rows come back, so a poll is not a page fetch.

It is not free on a large filtered table: the stamp is an aggregate over the filtered set, so an unindexed version column is a scan per tick. Index it, and index it together with whatever the grid filters on — the same reasoning that makes total opt-in applies here.

Polling stops while the browser tab is hidden (whileHidden: true opts back in), and never fires while a row is being edited — refreshing under someone's cursor would discard what they are typing. The change is picked up on the next poll after they finish.

Push instead of poll

revalidate() is the same code path the timer uses, so a broadcast event can drive it directly and you can leave polling off:

const grid = createGrid({ columns, fetch });          // no liveUpdates

Echo.private(`sales`).listen("SalesChanged", () => {
  void grid.revalidate();                              // checks the stamp, refetches if it moved
});

The package does not ship the transport. Broadcasting means your application emitting events on writes, which it has to do for its own tables — there is nothing generic for a package to do there.

One caveat worth knowing

The viewport is paged with skip/take. If rows are inserted or deleted above the current page, the same offset points at a slightly different slice, so a live grid can appear to jump by a row. That is inherent to offset paging under concurrent writes, not something the version stamp introduces.