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?
SSE is simpler than WebSockets—it's just HTTP! It works through proxies, load balancers, and firewalls without special configuration. Perfect for server-to-client streaming.

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>"}

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);
});

Event ID & Retry

Event IDs

Set an event ID for replay/resume capabilities:

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);
Note
The retry value is sent as part of the SSE protocol. The browser handles reconnection automatically.

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

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);
});

Each state() call inside the callback sends an event immediately, rather than batching them at the end.

Tip
See Streaming Mode for detailed coverage of long-running operations.

Debugging SSE

To inspect Gale's SSE traffic:

  1. Open your browser's DevTools
  2. Go to the Network tab
  3. Filter by EventStream or XHR
  4. Click on a Gale request
  5. 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
});

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>

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]);

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.

On this page