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

> Error hierarchy and when each error is thrown in the Node.js SDK.

The SDK distinguishes between two classes of failures:

1. **Initial sync failures** - surfaced as exceptions from `NexusClient.create()`. The application should refuse to start with an empty cache.
2. **Background sync / SSE failures** - never thrown. They are swallowed silently, 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-nodejs';

try {
  const value = client.getSecret('key');
} catch (err) {
  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)
    ├── NexusNotFoundError              (HTTP 404)
    ├── NexusPublicKeyError             (getSecret called with public key)
    ├── NexusSecretNotFoundError        (key absent from cache)
    ├── NexusRateLimitedError           (v0.5.0) (HTTP 429 on write endpoints)
    ├── NexusQuarantinedError           (v0.4.0) (HTTP 429 quarantine - sync path)
    └── NexusAbAddonNotAvailableError   (v0.4.0) (HTTP 403 on evaluateAB)
```

## When each is thrown

| Error                                         | Thrown by                                          | When                                                                                                                                                                                                     |
| --------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NexusInitError`                              | `NexusClient.create()`                             | The initial sync failed for any reason. The `cause` property carries the underlying error (often `NexusUnauthorizedError` or a transport-level `Error`).                                                 |
| `NexusUnauthorizedError`                      | `create()`, `evaluateFlags`, `evaluateAB`          | Server returned HTTP 401 - invalid API key, slug/key mismatch, or revoked key.                                                                                                                           |
| `NexusBillingError` *(v0.2.0)*                | `create()`, background sync                        | Server returned HTTP 402 - tenant has invoices overdue by more than 14 days. Background sync halts; the cache continues to serve. Check `client.billingOverdue`.                                         |
| `NexusNotFoundError`                          | `create()`, `evaluateFlags`, `evaluateAB`          | Server returned HTTP 404 - unknown subdomain or missing endpoint.                                                                                                                                        |
| `NexusPublicKeyError`                         | `getSecret()`, `getSecretFilePath()`               | The client uses a public key. Thrown synchronously, before the network call.                                                                                                                             |
| `NexusServiceKindMismatchError` *(v0.2.0)*    | `getSecret()`, `getSecretFilePath()`               | The service `kind` reported by the server is `"frontend"`. Frontend services must not hold secrets, regardless of key type.                                                                              |
| `NexusSecretNotFoundError`                    | `getSecret()`                                      | The requested key is not in the cache (or it's a file-type secret - use `getSecretFilePath` for those). The `key` property carries the requested name.                                                   |
| `NexusWIFNotConfiguredError` *(v0.2.0)*       | `create()`                                         | `wif.enabled = true` but no usable workload-identity provider matched the running environment, or an explicit `tokenSource` returned an empty token. Wrapped in `NexusInitError` on the initial connect. |
| `NexusWIFTokenExchangeFailedError` *(v0.2.0)* | `create()`, background sync                        | `POST /v1/auth/token-exchange` returned a non-2xx response, or the body could not be decoded. Wrapped in `NexusInitError` on the initial connect.                                                        |
| `NexusSessionExpiredError` *(v0.2.0)*         | background sync                                    | The WIF session JWT has expired and the SDK could not refresh it. Cache continues to serve; refresh is retried on the next read.                                                                         |
| `NexusRateLimitedError` *(v0.5.0)*            | `setSecret`, `deleteSecret`, `deleteSecretVersion` | Server returned HTTP 429 - too many write requests. Back off and retry.                                                                                                                                  |
| `NexusQuarantinedError` *(v0.4.0)*            | `_doSync` (sync path only)                         | Server returned `429` with quarantine body (`{"error":"quarantined",...}`). `reason` and `expiresAt` are available as typed properties. Background sync is suppressed until `expiresAt` passes.          |
| `NexusAbAddonNotAvailableError` *(v0.4.0)*    | `evaluateAB`                                       | Server returned HTTP 403 - the AB Testing add-on is not active for this project.                                                                                                                         |

## What is NOT thrown

* **Background syncs** - silently retried on the next TTL tick. Cache served.
* **SSE transport errors** - exponential backoff, 3-strike fallback to polling. No exception.
* **`304 Not Modified`** - treated as success; TTL reset.
* **Missing config / flag** - `getConfig` returns `undefined`; `getFlag` returns the `defaultValue`.
* **Unknown key in `evaluateFlags`** - resolves to `false` per the server contract; never throws.

<Info>
  The asymmetry between secrets (throw) and configs/flags (sentinel) is deliberate: a missing secret usually means "you're misconfigured, fail fast", while a missing flag usually means "use the safe default and continue".
</Info>

## Failure scenarios

### Wrong API key

```ts theme={null}
try {
  const client = await NexusClient.create({ endpoint, apiKey });
} catch (err) {
  if (err instanceof NexusInitError && err.cause instanceof NexusUnauthorizedError) {
    console.error('nexus: bad credentials - check NEXUS_API_KEY');
    process.exit(1);
  }
  throw err;
}
```

### Slug doesn't match key (Double-Gate failure)

In production, the backend validates that the slug embedded in `endpoint` matches the API key's service. A mismatch returns 401 - same surface as above.

### Network down at startup

If the Nexus backend is unreachable when `create()` runs, you get `NexusInitError` whose `cause` is a transport-level `Error` (`ECONNREFUSED`, `ENOTFOUND`, etc.).

```ts theme={null}
async function createWithRetry(config: NexusConfig, retries = 5) {
  for (let i = 0; i < retries; i++) {
    try {
      return await NexusClient.create(config);
    } catch (err) {
      if (i === retries - 1) throw err;
      await new Promise((r) => setTimeout(r, 1000 * 2 ** i));
    }
  }
  throw new Error('unreachable');
}
```

### Reading a secret with a public key

```ts theme={null}
try {
  const value = client.getSecret('DB_PASSWORD');
} catch (err) {
  if (err instanceof NexusPublicKeyError) {
    console.error('nexus: public keys cannot read secrets - use a secret key');
  }
}
```

This check fires synchronously, before the network call.

### Reading a secret that doesn't exist

```ts theme={null}
try {
  const value = client.getSecret('does.not.exist');
} catch (err) {
  if (err instanceof NexusSecretNotFoundError) {
    console.error(`missing secret: ${err.key}`);
  }
}
```

### Reading a non-existent flag or config

Returns the default - no error:

```ts theme={null}
client.getFlag('made.up.key', false);   // false
client.getConfig('made.up.key');        // undefined
```

## SSE-specific behaviour

SSE failures **never** propagate to your code. The SDK silently logs them and falls back to polling after 3 strikes. To detect "live updates are no longer available", check `client.streamStatus`:

```ts theme={null}
if (client.streamStatus === 'fallback') {
  console.warn('nexus: SSE has fallen back to TTL polling');
}
```
