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

# Vue SDK - SSE Live Updates

> How SSE live propagation works in the Vue SDK, including plugin teardown and Nuxt 3.

The SDK opens a persistent `fetch`-based SSE connection to `GET <baseUrl>/v1/stream` after the initial sync completes. The API key is sent as the `X-Nexus-API-Key` request header - the same header used by all other endpoints. Whenever the Nexus backend emits a change event (`config.updated`, `flag.toggled`, `resync`, ...), the SDK triggers an immediate full re-sync - so the in-memory cache is updated within milliseconds of a change. Components subscribed via composables re-render automatically.

## Authentication

The Vue SDK sends the API key as the `X-Nexus-API-Key` header on the stream connection. Unlike the browser `EventSource` API - which cannot set custom request headers - the `fetch` API supports headers on streaming responses, so no `?key=` query-parameter workaround is needed. The key never appears in the URL, server access logs, or browser history.

<Info>
  Secret keys (`wxs_…`) are rejected at client-creation time. Only public keys (`wxp_…`) may be used with browser SDKs.
</Info>

## Vue reactivity integration

Stream events are processed in the `fetch` reader loop, outside the Vue component lifecycle - but because the SDK's composables depend on an internal version `Ref` that's bumped on every snapshot replacement, this is invisible to your components. When an SSE event triggers a sync, the SDK swaps the snapshot atomically, bumps the version Ref, and Vue's reactivity system schedules re-renders.

You don't need to do anything special - just use the composables normally.

## Reconnection

The SDK manages reconnection itself - it does not rely on the browser `EventSource` auto-reconnect (there is no `EventSource`). After a transport failure the SDK schedules a reconnect with exponential backoff (1 s → 30 s capped). Any `retry:` field sent by the server is ignored.

On every (re)connect the server emits a `resync` event, which causes the SDK to call `sync()` once and align with the current state. There is no event history; missed events are not replayed.

After **3 consecutive transport errors** the SDK closes the stream and falls back to TTL polling, emitting `FALLBACK_REASON_MAX_ERRORS`. The SDK then automatically schedules an SSE reconnect using the `sseReconnectCooldown` schedule (default: 5 → 10 → 20 → 40 → 60 min). When the timer fires, the failure counter resets and `connectStream()` is called automatically. *(v0.6.0)*

```
nexus: SSE transport error (attempt 1/3)
nexus: SSE transport error (attempt 2/3)
nexus: SSE 3 consecutive errors - falling back to polling
nexus: SSE reconnect scheduled in 5 min
```

Use `useNexusStreamStatus()` to surface this to users:

```vue theme={null}
<script setup lang="ts">
import { useNexusStreamStatus } from '@westyx-nexus/sdk-vue';
const { connected } = useNexusStreamStatus().value;
</script>

<template>
  <span>{{ connected ? 'Live' : 'Polling' }}</span>
</template>
```

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

### Detection

Because the SDK opens the stream with `fetch`, it sees the HTTP status directly on the response. When the stream connection returns 429 the SDK:

1. Emits `FALLBACK_REASON_RATE_LIMITED` on the stream observer.
2. Checks for a `Retry-After` header (delta-seconds, clamped to `[5 s, 300 s]`):
   * **Present:** schedules a `connectStream()` re-entry after the indicated delay.
   * **Absent:** falls back to TTL polling and schedules reconnect via the `sseReconnectCooldown` schedule. *(v0.6.0)*
3. Does NOT reopen the stream until the delay elapses.

A 429 whose body is `{"error":"quarantined"}` instead fires `onQuarantined(reason, expiresAt)` and reconnects after the quarantine window.

## Disabling SSE

The SDK calls `connectStream()` automatically. To opt out at runtime via the imperative client:

```vue theme={null}
<script setup lang="ts">
import { useNexus } from '@westyx-nexus/sdk-vue';

const nexus = useNexus();

function goOffline() {
  nexus.disconnectStream();
}
</script>
```

## Plugin teardown (v0.3.1+)

Both `createNexusPlugin().install` and `provideNexus()` wrap the host `app.unmount()` so that `client.disconnectStream()` is called automatically when the Vue application is torn down. This aborts the fetch stream, clears any pending reconnect timer, and releases the underlying network connection.

You don't need to do anything as a consumer - teardown is invisible.

Two properties of this wrapper are worth knowing about:

* **Errors are swallowed.** Any throw from `disconnectStream()` is caught and logged via `console.warn`. The wrapper never propagates the error to `app.unmount()`'s caller.
* **The original `unmount` always runs.** Even if `disconnectStream()` throws, the wrapped `originalUnmount()` is still invoked, so Vue's normal cleanup proceeds.

This matters in two practical scenarios:

* **HMR.** Vite / webpack dev servers re-mount the root component on every hot module replacement. Before v0.3.1, each HMR cycle leaked one stream connection. The teardown wrapper now closes the previous stream automatically.
* **SSR / Nuxt 3 app shutdown.** When the Nuxt app instance is disposed (e.g. test teardown, dynamic remount), the SSE connection is now released deterministically instead of leaking until GC.

## What events trigger a sync

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

## SSR / Nuxt 3

The fetch-based SSE stream is browser-only (the SDK skips it during SSR). In Nuxt 3, register the SDK in a **client-only plugin**:

```ts theme={null}
// plugins/nexus.client.ts
import { provideNexus } from '@westyx-nexus/sdk-vue';

export default defineNuxtPlugin(async (nuxtApp) => {
  await provideNexus(nuxtApp.vueApp, {
    baseUrl: useRuntimeConfig().public.nexusUrl,
    apiKey:  useRuntimeConfig().public.nexusApiKey,
  });
});
```

The `.client.ts` suffix tells Nuxt to skip this plugin on the server, so SSR doesn't try to open the stream.
