npm package: @skdevlabs/pharos-sdk
Description: Official SDK for Pharos uptime and incident monitoring
Author: SKDEV Labs
Latest version: 0.0.3

# Pharos SDK

Official TypeScript/JavaScript SDK for [Pharos](https://pharos.sh) uptime and incident monitoring.

## Installation

```bash
npm install pharos-sdk
# or
pnpm add pharos-sdk
# or
yarn add pharos-sdk
```

## Getting Your API Key

Before using the SDK, you need to create an API key in the Pharos dashboard:

1. Log in to your Pharos account
2. Navigate to **Settings** > **API Keys**
3. Click **Create API Key**
4. Give your key a descriptive name (e.g., "Production Server", "CI/CD Pipeline")
5. Copy the API key immediately - you won't be able to see it again!
6. Store it securely in your environment variables

**Security Best Practices:**
- Never commit API keys to version control
- Store keys in environment variables (`.env` files)
- Use different keys for different environments (dev, staging, production)
- Rotate keys periodically
- Revoke keys immediately if compromised

## Quick Start

```typescript
import { PharosClient } from 'pharos-sdk';

const pharos = new PharosClient({
  apiKey: process.env.PHAROS_API_KEY!, // Use environment variables
  serviceName: 'my-service',
  environment: 'production',
});

// Send a health ping
await pharos.health.ping();
```

## Configuration

```typescript
const pharos = new PharosClient({
  // Required
  apiKey: process.env.PHAROS_API_KEY!, // Get from Pharos dashboard

  // Optional
  baseUrl: 'https://usepharos.online',    // Default: https://usepharos.online (or your self-hosted URL)
  serviceName: 'my-service',           // Default: 'unknown'
  environment: 'production',           // Default: 'production'
  timeout: 5000,                       // Default: 5000ms
  maxRetries: 3,                       // Default: 3
  retryDelay: 1000,                    // Default: 1000ms (exponential backoff)
});
```

### Environment Variables

Create a `.env` file in your project root:

```bash
PHAROS_API_KEY=phk_your_api_key_here
PHAROS_BASE_URL=https://usepharos.online  # Optional, defaults to https://usepharos.online
```

## API Key Management

### Creating API Keys

1. **Via Dashboard:** Go to Settings > API Keys > Create API Key
2. **Name Convention:** Use descriptive names that indicate the purpose
   - ✅ Good: `production-web-server`, `staging-worker`, `ci-cd-pipeline`
   - ❌ Bad: `key1`, `test`, `mykey`

### Rotating API Keys

To rotate your API keys without downtime:

1. Create a new API key in the dashboard
2. Update your application to use the new key
3. Deploy the changes
4. Revoke the old key after confirming the new one works

### Revoking API Keys

If a key is compromised or no longer needed:

1. Go to Settings > API Keys
2. Find the key you want to revoke
3. Click the revoke button
4. Confirm the action

**Note:** Revoking a key immediately blocks all API requests using that key.

## Features

### Health Endpoints

Create standardized, Pharos-compatible health endpoints with built-in dependency checking:

```typescript
// Basic health endpoint (no checks)
const response = await pharos.createHealthResponse();
// Returns: { status: "ok", timestamp: "...", service: "...", environment: "...", uptimeSec: 123 }

// Health endpoint with dependency checks
const response = await pharos.createHealthResponse({
  version: "1.0.0",
  checks: {
    db: {
      fn: async () => {
        await db.ping();
        return { status: "ok", latencyMs: 12 };
      },
      critical: true,  // Service is "down" if this fails
      timeout: 2000,
    },
    redis: async () => {
      // Shorthand format (non-critical by default)
      await redis.ping();
      return { status: "ok", latencyMs: 5 };
    },
  },
});
```

#### Next.js App Router

```typescript
// app/api/health/route.ts
import { PharosClient } from '@skdevlabs/pharos-sdk';
import { respondNextJs } from '@skdevlabs/pharos-sdk/adapters/nextjs';

const pharos = new PharosClient({
  apiKey: process.env.PHAROS_API_KEY!,
  serviceName: 'my-app',
});

export async function GET() {
  return await respondNextJs(pharos, {
    version: process.env.npm_package_version,
    checks: {
      db: async () => {
        await db.ping();
        return { status: 'ok' };
      }
    }
  });
}
```

#### Express

```typescript
import express from 'express';
import { PharosClient } from '@skdevlabs/pharos-sdk';

const app = express();
const pharos = new PharosClient({
  apiKey: process.env.PHAROS_API_KEY!,
  serviceName: 'my-express-app',
});

app.get('/health', async (req, res) => {
  const { statusCode, headers, body } = await pharos.respond({
    checks: {
      db: async () => {
        await db.ping();
        return { status: 'ok', latencyMs: 12 };
      }
    }
  });

  res.status(statusCode).set(headers).send(body);
});
```

#### Fastify

```typescript
import fastify from 'fastify';
import { PharosClient } from '@skdevlabs/pharos-sdk';

const app = fastify();
const pharos = new PharosClient({
  apiKey: process.env.PHAROS_API_KEY!,
  serviceName: 'my-fastify-app',
});

app.get('/health', async (request, reply) => {
  const { statusCode, headers, body } = await pharos.respond({
    checks: {
      db: async () => {
        await db.ping();
        return { status: 'ok' };
      }
    }
  });

  reply.code(statusCode).headers(headers).send(body);
});
```

#### Health Response Schema

All health endpoints return a standardized response:

```typescript
{
  status: "ok" | "degraded" | "down",
  timestamp: "2025-12-31T12:00:00.000Z",
  service: "my-service",
  environment: "production",
  version: "1.0.0",              // optional
  uptimeSec: 123.45,
  checks: {                      // optional
    db: { status: "ok", latencyMs: 12 },
    redis: { status: "ok", latencyMs: 5 }
  },
  meta: {                        // optional
    region: "us-west-2",
    instanceId: "i-123"
  }
}
```

**HTTP Status Codes:**
- `200`: Service is "ok" or "degraded"
- `503`: Service is "down" (critical check failed)

### Health Checks (Legacy)

Send lightweight health pings from your application's health endpoints:

```typescript
// In your Express health endpoint
app.get('/health', async (req, res) => {
  await pharos.health.ping();
  res.json({ status: 'ok' });
});

// With metadata
await pharos.health.ping({
  metadata: {
    version: '1.0.0',
    uptime: process.uptime(),
  },
});
```

### Job Tracking

Track background jobs, cron tasks, and scheduled workers:

```typescript
// Track a job lifecycle
const jobName = 'daily-backup';
const startTime = Date.now();

try {
  await pharos.jobs.start({ jobName });

  // ... perform your job ...

  await pharos.jobs.complete({
    jobName,
    duration: Date.now() - startTime,
    metadata: { filesProcessed: 1000 },
  });
} catch (error) {
  await pharos.jobs.fail({
    jobName,
    error: error.message,
    duration: Date.now() - startTime,
  });
}
```

### Incident Management

Create incidents programmatically when critical errors occur:

```typescript
// System-wide incident (no endpoint required)
await pharos.incidents.create({
  title: 'High memory usage detected',
  severity: 'medium',
  description: 'Application memory usage exceeded 80% threshold',
  metadata: {
    currentMemory: '850MB',
    threshold: '800MB',
  },
});

// Endpoint-specific incident (optional endpointId)
await pharos.incidents.create({
  title: 'Database connection failed',
  severity: 'high',
  description: 'Unable to connect to primary database after 3 retries',
  endpointId: 'your-endpoint-uuid', // Optional - only if incident relates to specific endpoint
  metadata: {
    region: 'us-east-1',
    errorCode: 'ECONNREFUSED',
  },
});
```

**Note:** `endpointId` is **optional**. Use it only when the incident relates to a specific monitored endpoint. Most SDK use cases (memory issues, database problems, etc.) don't require an endpoint.

Severity levels: `'low'` | `'medium'` | `'high'` | `'critical'`

## Reliability

The SDK includes built-in reliability features:

- **Automatic Retries**: Failed requests are automatically retried with exponential backoff
- **Timeout Handling**: Requests timeout after 5 seconds by default (configurable)
- **5xx Error Handling**: Automatically retries on server errors (5xx)
- **Network Error Handling**: Retries on network failures

## Error Handling

```typescript
try {
  await pharos.incidents.create({
    title: 'Critical error',
    severity: 'critical',
  });
} catch (error) {
  console.error('Failed to create incident:', error);
  // Your fallback logic here
}
```

## TypeScript Support

The SDK is written in TypeScript and provides full type definitions out of the box.

```typescript
import type {
  PharosConfig,
  IncidentSeverity,
  JobStartOptions,
} from 'pharos-sdk';
```

## Examples

### Express.js Health Endpoint

```typescript
import express from 'express';
import { PharosClient } from 'pharos-sdk';

const app = express();
const pharos = new PharosClient({
  apiKey: process.env.PHAROS_API_KEY!,
  serviceName: 'api-server',
});

app.get('/health', async (req, res) => {
  try {
    await pharos.health.ping();
    res.json({ status: 'healthy' });
  } catch (error) {
    res.status(500).json({ status: 'unhealthy' });
  }
});
```

### Cron Job Monitoring

```typescript
import { PharosClient } from 'pharos-sdk';

const pharos = new PharosClient({
  apiKey: process.env.PHAROS_API_KEY!,
  serviceName: 'cron-worker',
});

async function runDailyBackup() {
  const jobName = 'daily-backup';
  const startTime = Date.now();

  await pharos.jobs.start({ jobName });

  try {
    // Perform backup
    await performBackup();

    await pharos.jobs.complete({
      jobName,
      duration: Date.now() - startTime,
    });
  } catch (error) {
    await pharos.jobs.fail({
      jobName,
      error: error instanceof Error ? error.message : 'Unknown error',
      duration: Date.now() - startTime,
    });
    throw error;
  }
}
```

### Critical Error Alerting

```typescript
import { PharosClient } from 'pharos-sdk';

const pharos = new PharosClient({
  apiKey: process.env.PHAROS_API_KEY!,
  serviceName: 'payment-processor',
});

async function processPayment(amount: number) {
  try {
    // Process payment logic
    await chargeCustomer(amount);
  } catch (error) {
    // Create incident for payment failures
    await pharos.incidents.create({
      title: 'Payment processing failed',
      severity: 'critical',
      description: `Failed to process payment of $${amount}`,
      metadata: {
        amount,
        errorMessage: error.message,
        timestamp: new Date().toISOString(),
      },
    });
    throw error;
  }
}
```

## License

MIT