inertia-live-laravel maintained by freepeace13
freepeace13/inertia-live-laravel
Live Inertia pages driven by Spatie Event Sourcing projections. Declare once on the server which projection changes affect which page props; the package broadcasts a small "topic changed" signal and the client reloads only the affected props through the page's own controller.
The socket never carries model data, so policies, hidden attributes and per-user fields keep working unchanged.
Part of Inertia Live
| Package | Install | Role |
|---|---|---|
freepeace13/inertia-live-laravel (this repo) |
composer require freepeace13/inertia-live-laravel |
Server: topics, projector trait, broadcasting, ->live() |
@freepeace13/inertia-live-core |
npm install @freepeace13/inertia-live-core |
Framework-agnostic client |
@freepeace13/inertia-live-vue |
npm install @freepeace13/inertia-live-vue |
Vue 3 plugin and useLive() |
@freepeace13/inertia-live-react |
npm install @freepeace13/inertia-live-react |
React provider and useLive() |
Full documentation, a runnable demo and the client packages live in the main repository.
Requirements
- PHP 8.3, 8.4 or 8.5
- Laravel 12 or 13
inertiajs/inertia-laravel^2.0 or ^3.0spatie/laravel-event-sourcing^7.14- An Echo-compatible broadcaster (Reverb, Pusher or Ably)
Installation
composer require freepeace13/inertia-live-laravel
The service provider is auto-discovered. Optionally publish the config:
php artisan vendor:publish --tag=inertia-live-config
Broadcasting must be set up (php artisan install:broadcasting if it is not). See Installation.
Usage
1. Declare topics on events
use Freepeace13\InertiaLive\Attributes\LiveTopic;
#[LiveTopic('documents.{documentUuid}', props: ['document', 'activity'])]
final class DocumentRenamed extends ShouldBeStored
{
public function __construct(
public readonly string $documentUuid,
public readonly string $title,
) {}
}
{placeholders} are filled from the event's scalar properties. The attribute is repeatable and is private unless its pattern is registered with Live::publicTopic(). See Topics.
2. Mark changes in projectors
use Freepeace13\InertiaLive\Concerns\EmitsLiveChanges;
final class DocumentProjector extends Projector
{
use EmitsLiveChanges;
public function onDocumentRenamed(DocumentRenamed $event): void
{
DocumentReadModel::whereUuid($event->documentUuid)->update(['title' => $event->title]);
}
}
Signals are buffered, coalesced per topic and sent only after the handler returns and the database transaction commits. For events without the attribute, call $this->liveChanged('documents.'.$uuid, ['document']) inside a handler. See Projectors.
3. Authorize the topic
use Freepeace13\InertiaLive\Facades\Live;
Live::authorize('documents.{uuid}', fn (User $user, string $uuid) =>
$user->can('view', Document::whereUuid($uuid)->firstOrFail())
);
Topics are broadcast on private channels (private-live.{topic}). A private topic without an authorizer fails closed: the signal is not sent and a warning is logged. See Authorization.
4. Bind the topic to props
return Inertia::render('Documents/Show', [
'document' => DocumentResource::make($doc),
'activity' => fn () => $doc->activity()->latest()->limit(20)->get(),
])->live("documents.{$doc->uuid}", only: ['document', 'activity']);
->live() adds a _live prop with the channel, bound props and current cursor. See Page bindings.
What the package provides
| Class | Responsibility |
|---|---|
Attributes\LiveTopic |
Declares which topic an event affects |
TopicResolver |
Resolves topic templates against event properties |
Concerns\EmitsLiveChanges |
Projector trait that records changes after handlers return |
ChangeBuffer / ReplayBuffer |
Coalesce changes per topic; hold changes during replays |
ChangeFlusher |
After commit: take the next sequence number, check authorizer, rate limit, broadcast |
Broadcasting\LiveChangeBroadcast |
ShouldBroadcastNow event named live.changed, payload { topic, version, props } |
Cursor\CursorRepository |
Per-topic sequence numbers (default: cache store, needs atomic increment) |
LiveManager / Facades\Live |
Live::authorize() and the test fake |
LiveResponseMacro / LiveBindings |
Inertia\Response::live() and the _live prop |
Testing\LiveFake |
Recorder installed by Live::fake() |
Correctness guarantees
| Risk | Rule |
|---|---|
| Signal before data is committed | Flushed in DB::afterCommit, after the projector handler returns |
| Queued or several projectors on a topic | Each signal takes the topic's next sequence number, so none is dropped as stale |
| Render races a signal | Cursor is the topic's latest sequence number; the client drops signals at or below it |
| Event bursts | One signal per topic per request or job |
| Sender's own action | Excluded via X-Socket-ID, which the client adapters send |
| Projector replay | Signals suppressed; optional final signal per topic |
| Rate limit | Over max_signals_per_second, signals collapse into one trailing signal (needs a queue worker) |
| Rolled-back transaction | Its changes are discarded, nothing is broadcast |
The cursor store needs atomic increment (Redis, database, Memcached). See Consistency and Design decisions.
Configuration
// config/inertia-live.php
return [
'enabled' => env('INERTIA_LIVE_ENABLED', true),
'channel_prefix' => 'live',
'cursor_store' => env('INERTIA_LIVE_CURSOR_STORE'), // null = default cache
'max_signals_per_second' => 10,
'replay' => ['suppress' => true, 'final_signal' => false],
'debug' => env('APP_DEBUG', false), // logs every flushed signal
];
Invalid channel_prefix or max_signals_per_second values throw at boot. Details in Configuration.
Testing
use Freepeace13\InertiaLive\Facades\Live;
Live::fake();
$this->post(route('documents.rename', $doc), ['title' => 'Q4 plan']);
Live::assertChanged("documents.{$doc->uuid}", props: ['document']);
Live::assertNothingChangedFor('documents.other-uuid');
Live::assertChangedTimes("documents.{$doc->uuid}", 1);
See Testing.
Documentation
See the documentation index for installation, topics, projectors, authorization, consistency, configuration and troubleshooting.
Development
composer install
composer test # Pest + Orchestra Testbench
composer lint # Pint (use composer format to fix)
composer analyse # PHPStan / Larastan
License
MIT