> ## 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 - Error Handling

> Error hierarchy and handling NexusInitError in the Vue SDK.

The SDK distinguishes between two classes of failures:

1. **Initial sync failures** - surfaced as `NexusInitError` from `provideNexus()`. With `await provideNexus(...)`, the rejection prevents `app.mount()` from running.
2. **Background sync / SSE failures** - never thrown. They are logged via `NexusLogger.error(...)`, and the existing cache continues to be served.

## Error hierarchy

All SDK errors extend `NexusError`. A single `instanceof` check covers everything:

```ts theme={null}
import { NexusError } from '@westyx-nexus/sdk-vue';

if (err instanceof NexusError) {
  // SDK-specific failure - handle as needed
}
```

The full hierarchy:

```
Error
└── NexusError                          (base class)
    ├── NexusInitError                  (initial sync failure - cause has detail)
    ├── NexusUnauthorizedError          (HTTP 401)
    ├── NexusBillingError               (HTTP 402)
    ├── NexusAbAddonNotAvailableError   (HTTP 403 from evaluateAB)
    ├── NexusServerError                (HTTP 5xx on /sync, caught internally by the retry loop; parity-exported in v0.3.1)
    ├── NexusNotFoundError              (HTTP 404)
    ├── NexusQuarantinedError           (HTTP 429 quarantine - v0.4.0)
    ├── NexusPublicKeyError             (imperative getSecret with public key)
    ├── NexusServiceKindMismatchError   (imperative getSecret on frontend kind)
    └── NexusSecretNotFoundError        (imperative getSecret with unknown key)
```

## When each is thrown

| Error                                            | Thrown by                                   | When                                                                                                                                                                                                                                              |
| ------------------------------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NexusInitError`                                 | `provideNexus()`                            | The initial sync failed for any reason. The `cause` property carries the underlying error.                                                                                                                                                        |
| `NexusUnauthorizedError`                         | Initial sync, manual `nexus.sync()`         | Server returned HTTP 401 - invalid API key, slug/key mismatch, or revoked key.                                                                                                                                                                    |
| `NexusBillingError` *(v0.2.0)*                   | Initial sync, background sync, `evaluateAB` | Server returned HTTP 402 - tenant has invoices overdue by more than 14 days. Background sync halts; the cache continues to serve. Use `useNexusBillingOverdue()` to detect the state.                                                             |
| `NexusAbAddonNotAvailableError` *(v0.3.0)*       | `evaluateAB()`, `useEvaluateAB()`           | Server returned HTTP 403 from `/v1/flags/evaluate-ab` - the project does not have the AB Testing add-on enabled.                                                                                                                                  |
| `NexusQuarantinedError` *(v0.4.0)*               | `nexus.sync()`, initial sync                | Server returned HTTP 429 with `{"error":"quarantined"}` body. Carries `reason` (string) and `expiresAt` (Date). The SDK sets an internal quarantine guard and fires `onQuarantined` on the observer. Background sync is paused until `expiresAt`. |
| `NexusServerError` *(parity-exported in v0.3.1)* | `nexus.sync()` (caught internally)          | Server returned HTTP 5xx on `/sync`. The SDK catches this and schedules an exponential-backoff retry. Exported from the package root for `instanceof` checks in custom logging interceptors.                                                      |
| `NexusNotFoundError`                             | Initial sync, manual `nexus.sync()`         | Server returned HTTP 404 - unknown subdomain or missing endpoint.                                                                                                                                                                                 |
| `NexusPublicKeyError`                            | `useNexus().getSecret(...)`                 | The client uses a public key. Thrown synchronously by the imperative method. The composable `useSecret()` returns `undefined` instead.                                                                                                            |
| `NexusServiceKindMismatchError` *(v0.2.0)*       | `useNexus().getSecret(...)`                 | The service `kind` reported by the server is `"frontend"`. The composable `useSecret()` returns `undefined` instead.                                                                                                                              |
| `NexusSecretNotFoundError`                       | `useNexus().getSecret(...)`                 | The requested key is not in the cache. The composable `useSecret()` returns `undefined` instead.                                                                                                                                                  |

## Composables vs imperative - non-throwing vs throwing

```ts theme={null}
// Composables - never throw, return undefined for missing/inaccessible
const stripe = useSecret('stripe.key');         // ComputedRef<string | undefined>
const banner = useConfig<string>('banner');     // ComputedRef<string | undefined>
```

```ts theme={null}
// Imperative - throw on missing or inaccessible
const nexus = useNexus();
const stripe = nexus.getSecret('stripe.key');   // throws if missing or public key
```

Why? Vue components re-evaluate computeds on every reactive change. If `useSecret` threw on a public key, every render would throw, breaking the component tree.

## Handling `NexusInitError`

`provideNexus(app, config)` rejects on init failure. The simplest pattern is to catch at the `main.ts` level:

```ts theme={null}
// main.ts
import { createApp } from 'vue';
import { provideNexus, NexusInitError } from '@westyx-nexus/sdk-vue';
import App from './App.vue';
import FallbackApp from './FallbackApp.vue';

const app = createApp(App);

try {
  await provideNexus(app, config);
  app.mount('#app');
} catch (err) {
  if (err instanceof NexusInitError) {
    // Mount a degraded UI instead of crashing
    createApp(FallbackApp).mount('#app');
  } else {
    throw err;
  }
}
```

If you prefer to crash the app entirely (the default behaviour for misconfiguration), just don't catch - `app.mount('#app')` won't be called.

## What is NOT thrown

* **Background syncs** - silently retried on the next TTL tick. Cache served.
* **SSE transport errors** - the SDK reconnects with exponential backoff, then falls back to TTL polling after 3 consecutive failures. No exception surfaces.
* **`304 Not Modified`** - treated as success; TTL reset.
* **Composable reads for missing/inaccessible keys** - return `undefined` (or the `defaultValue` for `useFlag`).

## SSE-specific behaviour

SSE failures **never** propagate to your code. Use `useNexusStreamStatus()` to detect "live updates are no longer available":

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

<template>
  <Banner v-if="!status.connected">
    Live updates paused - values may lag up to 60 s
  </Banner>
</template>
```

## 5xx exponential backoff

When the server returns a `5xx` status on `/sync`, the SDK does not fail silently until the next TTL tick. Instead it:

1. Catches the response internally and surfaces it as a `NexusServerError(status, bodySnippet)`.

2. Increments an internal attempt counter and schedules a retry via `setTimeout` with **exponential backoff**:

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

3. Emits `FALLBACK_REASON_TRANSPORT` on the stream observer during the wait.

4. On the next successful sync - including the auto-retry succeeding - resets the counter.

```ts theme={null}
import { provideNexus, FALLBACK_REASON_TRANSPORT } from '@westyx-nexus/sdk-vue';

await provideNexus(app, {
  baseUrl: '...',
  apiKey:  'wxp_...',
  streamObserver: {
    onFallback(reason) {
      if (reason === FALLBACK_REASON_TRANSPORT) {
        // 5xx wait - surface "syncing..." in the UI
      }
    },
  },
});
```
