npm package: @lovettbarron/cnc
Description: Fire-and-forget monitoring client for [CandC](https://github.com/lovettbarron/candc). Sends heartbeats, errors, and logs to your CandC hub without ever blocking your application.
Latest version: 0.2.0

# @lovettbarron/cnc

Fire-and-forget monitoring client for [CandC](https://github.com/lovettbarron/candc). Sends heartbeats, errors, and logs to your CandC hub without ever blocking your application.

## Install

```bash
npm install @lovettbarron/cnc
# or
pnpm add @lovettbarron/cnc
```

## Quick Start

```typescript
import { createClient } from '@lovettbarron/cnc'

const candc = createClient({
  hubUrl: 'https://monitor.andrewlb.com',
  apiKey: 'your-api-key', // from POST /v1/apps registration
})

// Start heartbeat loop (default: every 30s)
candc.start()

// Report an error
candc.reportError(new Error('Payment failed'), {
  subsystem: 'payments',
  severity: 'high',
})

// Send a log entry
candc.log('error', 'Unexpected response from Stripe')

// On shutdown
candc.stop()
```

## API

### `createClient(config): CandcClient`

Creates a new CandC client instance.

**Config:**

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `hubUrl` | `string` | yes | CandC hub URL (e.g. `https://monitor.andrewlb.com`) |
| `apiKey` | `string` | yes | App API key from hub registration |
| `onError` | `(err: unknown) => void` | no | Called on non-timeout network errors. Timeouts are always silent. |

### `client.start(intervalMs?)`

Starts the heartbeat loop. Sends one heartbeat immediately, then repeats on the given interval (default: 30,000ms). The timer is `unref()`'d so it won't keep your process alive.

### `client.stop()`

Stops the heartbeat loop. Safe to call multiple times.

### `client.heartbeat()`

Sends a single heartbeat. Use this if you prefer manual control over the interval.

### `client.reportError(err, opts?)`

Reports an error to the hub. Accepts `Error` instances or any value (non-Error values are wrapped via `new Error(String(val))`).

**Options:**

| Option | Type | Description |
|--------|------|-------------|
| `subsystem` | `string` | Optional grouping label (e.g. `"payments"`, `"scraper"`) |
| `severity` | `'low' \| 'medium' \| 'high'` | Optional severity level |

### `client.log(level, message, fields?)`

Sends a log entry to the hub's Loki instance.

| Param | Type | Description |
|-------|------|-------------|
| `level` | `string` | Log level (e.g. `"info"`, `"error"`) |
| `message` | `string` | Log message |
| `fields` | `Record<string, string>` | Optional metadata (note: not forwarded to hub in current version) |

## Pino Transport

For apps using [Pino](https://getpino.io), the package includes a transport that automatically forwards warn-level logs and above. Errors (level 50+) are dual-pushed to both `/v1/logs` and `/v1/errors`.

```typescript
import pino from 'pino'

const logger = pino({
  transport: {
    targets: [
      { target: 'pino-pretty' },
      {
        target: '@lovettbarron/cnc/transport',
        options: {
          hubUrl: 'https://monitor.andrewlb.com',
          apiKey: 'your-api-key',
        },
      },
    ],
  },
})

// These are forwarded automatically:
logger.warn('High memory usage detected')    // → /v1/logs
logger.error(new Error('DB connection lost')) // → /v1/logs + /v1/errors

// These are NOT forwarded (below warn threshold):
logger.info('Request handled')
logger.debug('Cache hit')
```

### Transport behavior

- **Levels below 40 (warn):** Ignored
- **Levels 40-49 (warn):** Sent to `/v1/logs` only
- **Levels 50+ (error, fatal):** Sent to both `/v1/logs` and `/v1/errors`
- **Fatal (60+):** Reported with `severity: 'high'`; error (50) uses `severity: 'medium'`
- **Timeout:** 5 second per request, failures are silently swallowed

## Design Principles

- **Fire-and-forget:** All network calls are non-blocking and non-throwing. The hub being down will never crash or slow your app.
- **Zero dependencies:** The client uses native `fetch` (Node 18+). Only the Pino transport depends on `pino-abstract-transport`.
- **Unref'd timers:** The heartbeat interval won't prevent your process from exiting cleanly.

## Registering Your App

Before using the client, register your app with the hub:

```bash
curl -X POST https://monitor.andrewlb.com/v1/apps \
  -H 'Content-Type: application/json' \
  -d '{"name": "my-app"}'
```

The response includes your `apiKey`.

## Requirements

- Node.js 18+ (native `fetch` required)

## License

No license specified.