> ## Documentation Index
> Fetch the complete documentation index at: https://docs.westyx.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# PHP SDK - SSE Live Updates

> connectStream() semantics, FPM warning, events, backoff, and max-errors behaviour for the PHP SDK.

The SDK can hold a persistent `GET /v1/stream` connection open. Whenever the Nexus backend emits a change event (`config.updated`, `flag.toggled`, `secret.created`, `resync`, ...), the SDK triggers an immediate full sync - so the cache is updated within milliseconds of the change instead of waiting for the next TTL tick.

## FPM warning

<Warning>
  `connectStream()` **blocks the calling PHP process indefinitely**. Never call it from a PHP-FPM request handler. Blocking a worker means it cannot serve any further HTTP requests until the method returns (which may be never). Use `connectStream()` only in long-running CLI scripts, queue consumers (`php artisan queue:work`, `php bin/console messenger:consume`), or Swoole/ReactPHP-based workers.
</Warning>

## Enabling it

Call `connectStream()` after `NexusClient::create` returns:

```php theme={null}
$client = NexusClient::create(new NexusConfig(
    baseUrl: $_ENV['NEXUS_URL'],
    apiKey:  $_ENV['NEXUS_API_KEY'],
));

// This blocks until maxErrors consecutive failures, or until the process is killed.
$client->connectStream();
```

The method blocks the outer reconnect loop. When `$maxErrors` consecutive transport errors occur, the SDK falls back to TTL polling, sleeps the reconnect cooldown schedule, then retries the SSE connection automatically. The process never needs to be restarted or supervised externally for this to work.

### Custom max errors

```php theme={null}
$client->connectStream(maxErrors: 5); // tolerate up to 5 consecutive errors
```

## What happens under the hood

1. The SDK opens an HTTP streaming response to `<baseUrl>/v1/stream` with `Accept: text/event-stream` and either `X-Nexus-API-Key: <key>` or `Authorization: Bearer <session_jwt>` (when [WIF](workload-identity) is enabled).
2. The server immediately sends a `resync` event, which causes the SDK to call `sync()` to align with the current state.
3. The connection stays open. Every change event triggers another full sync.
4. While the stream is up, TTL polling continues as a safety net.

## Events table

| Event                                                 | Triggers sync | Description                                                                                      |
| ----------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------ |
| `resync`                                              | yes           | Server requests a full state refresh                                                             |
| `config.created` / `.updated` / `.deleted`            | yes           | A config value changed                                                                           |
| `flag.created` / `.updated` / `.toggled` / `.deleted` | yes           | A feature flag changed                                                                           |
| `secret.created` / `.updated` / `.deleted`            | yes           | A secret changed (secret keys only; filtered server-side for public keys)                        |
| `auth_expiring`                                       | no            | Session JWT is expiring soon (WIF only) - triggers a non-blocking token refresh, not a data sync |

<Note>
  The SDK does not apply event payloads directly to the cache - every event triggers a full re-sync that returns the current authoritative state. This avoids any drift between the SDK's view and the backend.
</Note>

## Reconnection and backoff

Any transport error (server restart, TCP drop, network blip) triggers a reconnect with exponential backoff:

| Attempt | Wait before retry |
| ------- | ----------------- |
| 1       | 1 s               |
| 2       | 2 s               |
| 3       | 4 s               |
| 4       | 8 s               |
| 5       | 16 s              |
| 6+      | 30 s (capped)     |

The backoff counter resets to zero after a successful connection is re-established.

## Max errors behaviour

After `$maxErrors` **consecutive** transport errors, the SDK falls back to TTL polling and enters the reconnect cooldown schedule. It then retries SSE automatically - no manual restart or wrapper loop is needed:

* Your application is never notified; reads continue to be served from the cache during the cooldown.
* The only visible effect during the cooldown is that propagation latency increases from milliseconds to the TTL.
* Once the cooldown elapses, the SDK re-opens the SSE stream and resumes sub-millisecond propagation.

The reconnect cooldown schedule is controlled by `$sseReconnectCooldown` (v0.6.0+). The default schedule is `[5, 10, 20, 40, 60]` minutes - each successive fallback waits longer before retrying. Pass an `int` for a fixed interval or an `array` for a custom schedule (the last value is reused indefinitely).

```php theme={null}
use WestyxNexus\NexusConfig;

// Default: [5, 10, 20, 40, 60] minute cooldown schedule
$client = NexusClient::create(new NexusConfig(
    baseUrl: $_ENV['NEXUS_URL'],
    apiKey:  $_ENV['NEXUS_API_KEY'],
));

// Fixed 10-minute cooldown between SSE reconnect attempts
$client = NexusClient::create(new NexusConfig(
    baseUrl:             $_ENV['NEXUS_URL'],
    apiKey:              $_ENV['NEXUS_API_KEY'],
    sseReconnectCooldown: 10,
));

// Custom schedule (stays at 60 min after the last entry)
$client = NexusClient::create(new NexusConfig(
    baseUrl:             $_ENV['NEXUS_URL'],
    apiKey:              $_ENV['NEXUS_API_KEY'],
    sseReconnectCooldown: [1, 2, 5],
));
```

## `auth_expiring` event (WIF)

When [WIF](workload-identity) is enabled, the backend emits an `auth_expiring` SSE control event approximately 5 minutes before the session JWT expires. The SDK reacts by triggering a non-blocking session refresh. When the server force-closes the stream at expiry, the SDK reconnects automatically with the freshly-issued bearer token.

## Disabling SSE

Just don't call `connectStream()`. The SDK runs in pure TTL-polling mode. For most PHP-FPM applications this is the correct choice - the TTL is short enough that changes appear within one polling interval.

## Laravel queue worker example

```php theme={null}
// app/Console/Commands/NexusDaemon.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use WestyxNexus\NexusClient;
use WestyxNexus\NexusConfig;

class NexusDaemon extends Command
{
    protected $signature = 'nexus:daemon';

    public function handle(): void
    {
        $client = NexusClient::create(new NexusConfig(
            baseUrl: config('services.nexus.url'),
            apiKey:  config('services.nexus.key'),
        ));

        $this->info('nexus: SSE stream starting');
        // Blocks indefinitely. After max errors the SDK falls back to TTL polling,
        // sleeps the reconnect cooldown, then retries SSE automatically (v0.6.0+).
        $client->connectStream();
    }
}
```

Run with `php artisan nexus:daemon` under a process supervisor (Supervisor, systemd) that restarts on exit.
