Streaming Mode

Gale's streaming mode allows you to send updates to the client in real-time during long-running operations. Events are sent immediately as they're added, enabling live progress updates, logs, and incremental data loading.

Basic Streaming

Use stream() to wrap your long-running operation. Inside the callback, all state updates and events are sent immediately to the client.

public function processUsers()
{
    return gale()->stream(function ($gale) {
        $users = User::all();
        $total = $users->count();
        $processed = 0;

        foreach ($users as $user) {
            $user->processExpensiveOperation();
            $processed++;

            // Sent immediately to client
            $gale->state('progress', [
                'current' => $processed,
                'total' => $total,
                'percent' => round(($processed / $total) * 100),
            ]);
        }

        $gale->state('complete', true);
        $gale->messages(['_success' => "Processed {$total} users"]);
    });
}

Frontend Implementation

The frontend receives updates in real-time. Use Alpine.js to display progress:

<div
    x-data="{
        progress: { current: 0, total: 0, percent: 0 },
        complete: false
    }">

    <button @click="$postx('/process-users')" x-loading.attr="disabled">
        <span x-loading.remove>Start Processing</span>
        <span x-loading>Processing...</span>
    </button>

    <!-- Live progress bar -->
    <div x-show="progress.total > 0 && !complete" class="mt-4">
        <div class="flex justify-between mb-1">
            <span x-text=`Processing ${progress.current} of ${progress.total}`></span>
            <span x-text="progress.percent + '%'"></span>
        </div>
        <div class="h-3 bg-gray-200 rounded-full overflow-hidden">
            <div
                class="h-full bg-blue-500 transition-all duration-150"
                :style="'width: ' + progress.percent + '%'"></div>
        </div>
    </div>

    <!-- Success message -->
    <div x-message="_success" class="mt-4 p-4 bg-green-100 text-green-700 rounded"></div>
</div>

Accumulation vs Streaming Mode

In normal mode, Gale accumulates all events and sends them at the end of the request. In streaming mode, events are sent immediately as they're added.

Feature Normal Mode Streaming Mode
Event delivery All at once (end of request) Immediately as added
Use case Quick operations Long-running tasks
Progress updates Not visible Real-time
Connection Single response Kept open
Memory usage Events buffered Events sent & freed

Pre-Stream Events

Events added before stream() is called are flushed when streaming begins:

return gale()
    // These events are sent when streaming starts
    ->state('status', 'initializing')
    ->state('startedAt', now())

    // Then streaming mode begins
    ->stream(function ($gale) {
        foreach ($items as $item) {
            $gale->state('status', "Processing {$item->name}");
            // Process item...
        }

        $gale->state('status', 'complete');
    });

Memory-Efficient Processing

Use Laravel's cursor() for memory-efficient processing of large datasets:

return gale()->stream(function ($gale) {
    // Cursor uses lazy loading - only one model in memory at a time
    $users = User::cursor();
    $total = User::count();
    $processed = 0;

    foreach ($users as $user) {
        $user->expensiveOperation();
        $processed++;

        // Update every 10 records to reduce network overhead
        if ($processed % 10 === 0) {
            $gale->state('progress', [
                'current' => $processed,
                'total' => $total,
            ]);
        }
    }

    $gale->state('complete', true);
});

Exception Handling

Exceptions in streaming mode are automatically captured and displayed with full stack traces. The error is rendered as a native Laravel exception page via SSE.

return gale()->stream(function ($gale) {
    foreach ($items as $item) {
        $gale->state('current', $item->id);

        // If an exception occurs, Gale renders it and sends to browser
        if ($item->isFailed()) {
            throw new \Exception("Failed to process item {$item->id}");
        }

        $item->process();
    }
});

// Or handle exceptions gracefully
return gale()->stream(function ($gale) {
    $errors = [];

    foreach ($items as $item) {
        try {
            $item->process();
        } catch (\Exception $e) {
            $errors[] = $e->getMessage();
            $gale->state('errors', $errors);
        }
    }

    if (count($errors) > 0) {
        $gale->messages(['_warning' => count($errors) . ' items failed']);
    }
});

Debugging with dd() and dump()

Laravel's dd() and dump() work in streaming mode. Output is captured and displayed in the browser.

return gale()->stream(function ($gale) {
    $users = User::with('orders')->get();

    // dump() output is captured and sent via SSE
    dump($users->first());

    foreach ($users as $user) {
        // Debugging in the middle of processing
        if ($user->id === 42) {
            dd($user, $user->orders);  // Stops execution, shows in browser
        }

        $gale->state('processed', $user->id);
    }
});

Redirects in Streaming Mode

Redirects work in streaming mode via JavaScript. When you call Laravel's redirect helpers, Gale intercepts them and performs client-side navigation.

return gale()->stream(function ($gale) {
    foreach ($items as $item) {
        $gale->state('processing', $item->id);
        $item->process();
    }

    // These all work in streaming mode
    return redirect('/dashboard');
    // return redirect()->route('users.index');
    // return redirect()->back();
});

Live Logging

Stream logs to the browser in real-time during processing:

return gale()->stream(function ($gale) {
    $logs = [];

    $log = function($message) use ($gale, &$logs) {
        $logs[] = [
            'time' => now()->format('H:i:s'),
            'message' => $message,
        ];
        $gale->state('logs', $logs);
    };

    $log('Starting import...');

    foreach ($records as $record) {
        $log("Importing record {$record->id}");
        $record->import();
    }

    $log('Import complete!');
});
<!-- Live log display -->
<div x-data="{ logs: [] }" class="max-h-64 overflow-y-auto bg-gray-900 rounded p-4">
    <template x-for="log in logs" :key="log.time + log.message">
        <div class="text-sm font-mono text-green-400">
            <span class="text-gray-500" x-text="'[' + log.time + ']'"></span>
            <span x-text="log.message"></span>
        </div>
    </template>
</div>

Streaming Features Summary

Feature Behavior
Events Sent immediately as added
dd() / dump() Output captured and displayed in browser
Exceptions Rendered with full stack traces
Redirects Work via JavaScript navigation
Pre-stream events Flushed when streaming begins
Connection Kept open until callback completes

On this page