> ## 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.

# Node.js SDK - SSE Live Updates

> How SSE live propagation works in the Node.js SDK, including 429 handling.

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

## Auth

Node.js uses the **`X-Nexus-API-Key` header** on all endpoints, including `/stream`, via the native `http`/`https` modules. The key is never passed as a `?key=` query parameter. (All Nexus SDKs send the key in this header - the browser SDKs do too, using fetch-based streaming rather than the `EventSource` API.)

## What happens under the hood

1. The SDK opens an HTTP streaming response to `<endpoint>/v1/stream` with `Accept: text/event-stream` and the `X-Nexus-API-Key` header.
2. The server immediately sends a `resync` event, which causes the SDK to call `_sync()` once to align with the current state.
3. The connection stays open. Every change event triggers another full sync.
4. While the stream is up, the polling interval is bumped to 60 s as a safety net.

## Reconnection

Any transport error (server restart, TCP drop, network blip) triggers a reconnect with **exponential backoff**: 1s - 2s - 4s - 8s - 16s - 30s (capped at 30s).

After **three consecutive transport errors** the SDK closes the stream and enters a reconnect cooldown. Reads continue to be served from the cache (propagation latency increases to the polling TTL), but the stream is not abandoned permanently. The SDK sleeps for the next interval in the `sseReconnectCooldown` schedule (default: 5 min, then 10, 20, 40, 60 min, staying at 60 min thereafter) and then automatically reopens `/v1/stream`.

```
client.streamStatus === 'fallback'   // SSE is in cooldown; TTL polling is active
```

The reconnect schedule is controlled by the `sseReconnectCooldown` config field (unit: minutes). The default schedule `[5, 10, 20, 40, 60]` means the first retry happens after 5 minutes, the second after 10, and so on - capping at 60 minutes for all subsequent attempts. Pass a single number for a fixed interval, or a custom array for a different progression. See [Configuration](configuration) for the full field reference.

## `429 Too Many Requests`

Every project tier has a per-service stream connection limit:

| Tier   | Connections |
| ------ | ----------- |
| `free` | 5           |
| `xs`   | 10          |
| `s`    | 25          |
| `m`    | 50          |
| `l`    | 150         |
| `xl`   | 500         |

Exceeding the limit returns `429 Too Many Requests`. Behaviour depends on whether the response carries a parseable `Retry-After` header.

### Behaviour (v0.6.0+)

**Quarantine `429`** (`{"error":"quarantined","reason":"...","expires_at":"..."}` body):

1. The SDK reads the response body and detects the quarantine payload.
2. It fires `observer.onQuarantined(reason, expiresAt)` - NOT `onFallback`.
3. It schedules an automatic reconnect at `expiresAt` (minimum 5 s). This path does **not** count toward `MAX_FAILURES`.
4. When the timer fires, the SDK reopens `/v1/stream` normally.

**Rate-limit `429` with a parseable `Retry-After` header** (delta-seconds, e.g. `Retry-After: 30`):

1. The SDK parses the header and clamps the delay to the range `[5 s, 5 min]`.
2. It fires `observer.onFallback(FALLBACK_REASON_RATE_LIMITED)` so dashboards and load-test tooling can record the episode.
3. It schedules a single `setTimeout`-based reconnect after the clamped delay. This path does **not** touch `backoffMs` or `failedAttempts` - Retry-After is cooperative back-pressure, not a transport error, so it does NOT count toward `MAX_FAILURES`.
4. When the timer fires, the SDK reopens `/v1/stream` normally.

**Without a parseable `Retry-After` header**:

1. The SDK fires `observer.onFallback(FALLBACK_REASON_RATE_LIMITED)`.
2. `client.streamStatus` becomes `'fallback'` and the stream is closed.
3. The SDK schedules a reconnect using the `sseReconnectCooldown` schedule (default: 5 min → 10 → 20 → 40 → 60 min). When the timer fires, the SDK reopens `/v1/stream` normally.

```ts theme={null}
import {
  NexusClient,
  FALLBACK_REASON_RATE_LIMITED,
  type StreamObserver,
} from '@westyx-nexus/sdk-nodejs';

const observer: StreamObserver = {
  onFallback(reason) {
    if (reason === FALLBACK_REASON_RATE_LIMITED) {
      // fires on BOTH the cooperative-reconnect path AND the terminate path
      metrics.increment('nexus.sse.rate_limited');
    }
  },
};

const client = await NexusClient.create({
  endpoint: 'https://...',
  apiKey:   'wxs_...',
  streamObserver: observer,
});
```

## Disabling SSE

```ts theme={null}
client.disconnectStream();
```

Useful if you want to react to a "go offline" / "reduce traffic" signal without disposing the whole client. Reconnect later with `client.connectStream()`.

## What events trigger a sync

| Event                                                 | secret keys | public keys          |
| ----------------------------------------------------- | ----------- | -------------------- |
| `resync`                                              | yes         | yes                  |
| `config.created` / `.updated` / `.deleted`            | yes         | yes                  |
| `flag.created` / `.updated` / `.toggled` / `.deleted` | yes         | yes                  |
| `secret.created` / `.updated` / `.deleted`            | yes         | filtered server-side |

## Lifecycle

The stream is bound to the `NexusClient` instance. `client.close()` cancels the connection along with the polling timer and the temp-file cleanup. Always call `close()` on shutdown:

```ts theme={null}
process.on('SIGTERM', async () => {
  await client.close();
  process.exit(0);
});
```

## Inspecting status

```ts theme={null}
client.streamStatus    // 'connected' | 'fallback' | 'disconnected'
```

| Value            | Meaning                                                                                |
| ---------------- | -------------------------------------------------------------------------------------- |
| `'connected'`    | The SSE stream is up and pushing events                                                |
| `'fallback'`     | 3 consecutive transport errors; SSE is in reconnect cooldown and TTL polling is active |
| `'disconnected'` | Stream is not active (either never started or `disconnectStream()` was called)         |
