SSE Events
Understanding Server-Sent Events and Gale's event protocol.
Overview
Gale uses Server-Sent Events (SSE) to stream data from your server to the browser. Unlike traditional AJAX where the server sends one response, SSE keeps the connection open allowing multiple events to be sent over time.
Why SSE?
Gale Event Types
Gale sends four types of SSE events:
| Event Type | Purpose | Triggered By |
|---|---|---|
gale-patch-state |
Update Alpine state | gale()->state() |
gale-patch-elements |
Update DOM elements | gale()->view(), html(), etc. |
gale-patch-component |
Update named component | gale()->componentState() |
gale-invoke-method |
Call component method | gale()->componentMethod() |
Event Format
Each SSE event follows this format:
event: gale-patch-state
id: evt_123abc
data: {"count":42,"message":"Hello"}
event: gale-patch-elements
data: {"selector":"#content","mode":"morph","html":"<div>...</div>"}
event: gale-patch-state
id: evt_123abc
data: {"count":42,"message":"Hello"}
event: gale-patch-elements
data: {"selector":"#content","mode":"morph","html":"..."}
The browser's EventSource API parses these events and Alpine Gale handles them appropriately.
Connection Lifecycle
Gale dispatches JavaScript events during the request lifecycle:
| Event | When Fired |
|---|---|
gale:started |
Request initiated |
gale:finished |
Request completed successfully |
gale:error |
Error occurred |
gale:retrying |
Retrying after failure |
gale:retries-failed |
Max retries exceeded |
Listening to Events
document.addEventListener('gale:started', (e) => {
console.log('Request started');
});
document.addEventListener('gale:finished', (e) => {
console.log('Request finished');
// Great place to trigger post-update actions
});
document.addEventListener('gale:error', (e) => {
console.error('Gale error:', e.detail);
});
document.addEventListener('gale:started', (e) => {
console.log('Request started');
});
document.addEventListener('gale:finished', (e) => {
console.log('Request finished');
// Great place to trigger post-update actions
});
document.addEventListener('gale:error', (e) => {
console.error('Gale error:', e.detail);
});
Event ID & Retry
Event IDs
Set an event ID for replay/resume capabilities:
return gale()
->withEventId('evt_' . uniqid())
->state('data', $data);
return gale()
->withEventId('evt_' . uniqid())
->state('data', $data);
If the connection drops, the browser can send the last event ID to resume from that point.
Retry Interval
Configure how long the browser waits before reconnecting:
return gale()
->withRetry(3000) // 3 seconds
->state('data', $data);
return gale()
->withRetry(3000) // 3 seconds
->state('data', $data);
Note
Response Headers
Gale automatically sets the required SSE headers:
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
X-Accel-Buffering: no
Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive X-Accel-Buffering: no
The X-Accel-Buffering: no header prevents nginx from buffering the response,
ensuring events stream immediately.
Streaming Mode
For long-running operations, use streaming mode to send events as they happen:
return gale()->stream(function ($gale) {
foreach ($items as $i => $item) {
// Process item...
$gale->state('progress', ($i + 1) / count($items) * 100);
$gale->state('status', "Processing {$item->name}...");
}
$gale->state('status', 'Complete!');
$gale->state('progress', 100);
});
return gale()->stream(function ($gale) {
foreach ($items as $i => $item) {
// Process item...
$gale->state('progress', ($i + 1) / count($items) * 100);
$gale->state('status', "Processing {$item->name}...");
}
$gale->state('status', 'Complete!');
$gale->state('progress', 100);
});
Each state() call inside the callback sends an event immediately,
rather than batching them at the end.
Tip
Debugging SSE
To inspect Gale's SSE traffic:
- Open your browser's DevTools
- Go to the Network tab
- Filter by EventStream or XHR
- Click on a Gale request
- Select the EventStream tab to see individual events
In Chrome, you'll see each event with its type, data, and timing.
Error Handling
Gale automatically retries failed requests with exponential backoff. You can configure this behavior:
// Configure retry behavior
$postx('/api/data', {
retryInterval: 1000, // Initial retry delay (ms)
retryScaler: 2, // Multiply delay by this each retry
retryMaxWaitMs: 30000, // Max wait between retries
retryMaxCount: 10, // Give up after this many retries
});
// Configure retry behavior
$postx('/api/data', {
retryInterval: 1000, // Initial retry delay (ms)
retryScaler: 2, // Multiply delay by this each retry
retryMaxWaitMs: 30000, // Max wait between retries
retryMaxCount: 10, // Give up after this many retries
});
You can also check retry status using the $gale magic:
<div x-data>
<span x-show="$gale.retrying">Reconnecting...</span>
<span x-show="$gale.retriesFailed">Connection lost</span>
</div>
<div x-data>
<span x-show="$gale.retrying">Reconnecting...</span>
<span x-show="$gale.retriesFailed">Connection lost</span>
</div>
Sending Multiple Events
Gale sends each action as a separate SSE event, allowing the browser to process them sequentially:
return gale()
// Event 1: Update state
->state('loading', false)
->state('count', 42)
// Event 2: Update DOM
->view('partials.results', compact('results'), [
'selector' => '#results',
])
// Event 3: Update another component
->componentState('sidebar', ['active' => true]);
return gale()
// Event 1: Update state
->state('loading', false)
->state('count', 42)
// Event 2: Update DOM
->view('partials.results', compact('results'), [
'selector' => '#results',
])
// Event 3: Update another component
->componentState('sidebar', ['active' => true]);
State patches are combined into a single event, while DOM patches and component operations are sent as separate events in the order they were called.