npm package: @infrasamurai/sdk-js
Description: TypeScript SDK for InfraSamurai metrics, alerting, and APM tracing
Latest version: 0.0.1

# @infrasamurai/sdk-js

TypeScript SDK for InfraSamurai metrics, alerting, and APM tracing.

## Installation

```bash
npm install @infrasamurai/sdk-js
```

## Features

| Entry Point | Purpose | Runtime |
|-------------|---------|---------|
| `@infrasamurai/sdk-js` | Core client (metrics & alerts) | Node.js, Browser, Deno, Bun |
| `@infrasamurai/sdk-js/node` | APM auto-instrumentation | Node.js only |
| `@infrasamurai/sdk-js/browser` | Browser APM tracing | Browser only |
| `@infrasamurai/sdk-js/queues` | Queue metrics | Node.js, Deno, Bun |

---

## Quick Start

### Metrics & Alerts

```typescript
import { InfraSamuraiClient } from "@infrasamurai/sdk-js";

const client = new InfraSamuraiClient({
  dsn: "your-project-dsn",
  environment: "production",
});

// Send a metric
await client.sendMetric({
  metricType: "photo_upload_speed",
  value: 2.5,
  unit: "MB/s",
});

// Send alerts
await client.critical("Server down!");
await client.error("Failed to process payment");
await client.warning("High memory usage");
await client.info("User signed in");
```

### Node.js APM (Auto-Instrumentation)

```typescript
// tracing.ts - Create this file and import it FIRST in your entry point
import { InfraSamuraiNode } from "@infrasamurai/sdk-js/node";

InfraSamuraiNode.init({
  dsn: process.env.ALERTSAMURAI_DSN!,
  serviceName: "my-api",
  serviceVersion: "1.0.0",
  environment: process.env.NODE_ENV,
});

// main.ts
import "./tracing"; // MUST be first import!
import { NestFactory } from "@nestjs/core";
// ... rest of your app
```

### Browser APM

```typescript
import { InfraSamuraiBrowser } from "@infrasamurai/sdk-js/browser";

InfraSamuraiBrowser.init({
  dsn: "your-project-dsn",
  serviceName: "my-frontend",
  environment: "production",
});

// Set user context for all traces
InfraSamuraiBrowser.setUser({
  id: "user-123",
  email: "user@example.com",
});
```

### Queue Metrics

```typescript
import { InfraSamuraiQueues } from "@infrasamurai/sdk-js/queues";

const queues = InfraSamuraiQueues.init({
  dsn: process.env.ALERTSAMURAI_DSN!,
  environment: "production",
});

// Record queue metrics periodically
setInterval(async () => {
  const stats = await getQueueStats();
  await queues.recordMetrics("orders-queue", {
    depth: stats.pending,
    consumerLag: stats.lag,
    processedCount: stats.processed,
    failedCount: stats.failed,
  });
}, 60000);
```

---

## Core Client

### Configuration

```typescript
const client = new InfraSamuraiClient({
  dsn: "your-dsn",           // Required - found in project settings
  environment: "production", // Optional - default environment
  baseUrl: "https://...",    // Optional - API endpoint
  timeout: 30000,            // Optional - request timeout (ms)
  retries: 3,                // Optional - retry attempts
  silent: true,              // Optional - suppress console warnings
});
```

### Metrics API

```typescript
await client.sendMetric({
  metricType: "photo_upload_speed", // Required - metric name (max 100 chars)
  value: 2.5,                       // Required - numeric value
  unit: "MB/s",                     // Optional - unit (max 50 chars)
  environment: "production",        // Optional - overrides default
  metadata: { userId: "123" },      // Optional - additional context
  timestamp: new Date(),            // Optional - defaults to now
});
```

### Alerts API

```typescript
await client.sendAlert({
  message: "Database connection failed", // Required (max 4000 chars)
  priority: "critical",                  // critical|error|warning|info|debug
  environment: "production",             // Optional
  data: { errorCode: "ECONNREFUSED" },   // Optional context
});

// Convenience methods
await client.critical("Server down!");
await client.error("Payment failed", { orderId: "123" });
await client.warning("High memory usage");
await client.info("User signed in");
await client.debug("Cache miss");
```

### Error Handling

The client is fault-tolerant - it never throws errors that could crash your application:

```typescript
const result = await client.sendMetric({ metricType: "test", value: 123 });

if (!result.success) {
  console.error("Failed:", result.error);
  // result.disabled === true if DSN is missing
}
```

For explicit error handling:

```typescript
import {
  InfraSamuraiError,
  AuthenticationError,  // 401 - Invalid DSN
  ValidationError,      // 400 - Invalid request
  NetworkError,         // Timeout, connection failure
} from "@infrasamurai/sdk-js";
```

### Retry Logic

- Default: 3 retries with exponential backoff (1s → 2s → 4s, max 10s)
- Does NOT retry on 401 (auth) or 400 (validation) errors

---

## Node.js APM

### Configuration

```typescript
import { InfraSamuraiNode } from "@infrasamurai/sdk-js/node";

InfraSamuraiNode.init({
  // Required
  dsn: process.env.ALERTSAMURAI_DSN!,
  serviceName: "my-api",

  // Optional
  serviceVersion: "1.0.0",
  environment: "production",
  baseUrl: "https://monitor.infrasamurai.com",

  // Sampling (0.0 to 1.0, default: 1.0)
  sampleRate: 0.1,         // Trace 10% of requests
  errorsSampleRate: 1.0,   // Capture 100% of errors
  sessionSampleRate: 1.0,  // Sample by instance

  // Auto-instrumentation toggles
  instrumentations: {
    http: true,      // HTTP incoming/outgoing (default: true)
    express: true,   // Express.js (default: true)
    nestjs: true,    // NestJS (default: true)
    prisma: true,    // Prisma ORM (default: true)
    redis: true,     // Redis/ioredis (default: true)
    pg: true,        // PostgreSQL (default: true)
    grpc: true,      // gRPC (default: true)
    fs: false,       // Filesystem (default: false - noisy)
    dns: false,      // DNS lookups (default: false - noisy)
  },

  // Custom attributes for all spans
  resourceAttributes: {
    "deployment.region": "us-east-1",
  },

  silent: false,  // Suppress warnings
  debug: true,    // Verbose logging
});
```

### Auto-Instrumented Libraries

The Node.js SDK automatically traces:

| Library | What's Captured |
|---------|-----------------|
| HTTP/HTTPS | All incoming requests, outgoing calls |
| Express | Routes, middleware, errors |
| NestJS | Controllers, guards, interceptors |
| Prisma | All database queries with statements |
| PostgreSQL (pg) | Raw SQL queries |
| Redis/ioredis | All Redis commands |
| gRPC | Client and server calls |
| MySQL | Database queries |
| MongoDB | Database operations |

### Manual Error Capture

```typescript
try {
  await riskyOperation();
} catch (error) {
  InfraSamuraiNode.captureError(error, {
    component: "PaymentService",
    action: "processPayment",
  });
}
```

### Shutdown

```typescript
// Flush pending spans before exit (for serverless)
await InfraSamuraiNode.flush();

// Full shutdown
await InfraSamuraiNode.shutdown();
```

---

## Browser APM

### Configuration

```typescript
import { InfraSamuraiBrowser } from "@infrasamurai/sdk-js/browser";

InfraSamuraiBrowser.init({
  // Required
  dsn: "your-project-dsn",
  serviceName: "my-frontend",

  // Optional
  serviceVersion: "1.0.0",
  environment: "production",

  // Sampling
  sampleRate: 0.1,        // Trace 10% of sessions
  errorsSampleRate: 1.0,  // Capture all errors

  // Auto-instrumentation
  instrumentations: {
    documentLoad: true,      // Page load metrics (LCP, FCP, TTFB)
    fetch: true,             // Fetch/XHR requests
    userInteraction: false,  // Clicks, form submissions (noisy)
    errors: true,            // Uncaught errors, unhandled rejections
  },

  // Distributed tracing - add trace headers to these URLs
  propagateTraceHeaderUrls: [
    /^https:\/\/api\.example\.com/,
    window.location.origin,
  ],

  // URLs to ignore
  ignoreUrls: [
    /\/health$/,
    /google-analytics\.com/,
  ],
});
```

### User Context

```typescript
// Set user after login
InfraSamuraiBrowser.setUser({
  id: "user-123",
  email: "user@example.com",
  username: "john_doe",
  plan: "premium",  // Custom attributes
});

// Clear on logout
InfraSamuraiBrowser.clearUser();

// Get current user
const user = InfraSamuraiBrowser.getUser();
```

### Manual Tracing

```typescript
// Wrap async operations
const result = await InfraSamuraiBrowser.trace(
  "checkout.process",
  async (span) => {
    span.setAttribute("cart.items", 5);
    return await processCheckout();
  }
);
```

### Manual Error Capture

```typescript
InfraSamuraiBrowser.captureError(error, {
  component: "CheckoutForm",
  action: "submit",
});
```

---

## Queue Metrics

For monitoring Kafka, RabbitMQ, SQS, BullMQ, or any message queue.

### Configuration

```typescript
import { InfraSamuraiQueues } from "@infrasamurai/sdk-js/queues";

const queues = InfraSamuraiQueues.init({
  dsn: process.env.ALERTSAMURAI_DSN!,
  environment: "production",
  timeout: 10000,  // Request timeout (ms)
  silent: false,
});
```

### Recording Metrics

```typescript
// Full metrics
await queues.recordMetrics("orders-queue", {
  depth: 150,                    // Messages waiting
  consumerLag: 50,               // Messages behind
  dlqCount: 3,                   // Dead letter queue count
  oldestMessageAgeSeconds: 120,  // Age of oldest message
  processedCount: 1000,          // Successfully processed
  failedCount: 5,                // Failed messages
  avgProcessingTimeMs: 45,       // Average processing time
  enqueueRate: 10.5,             // Messages/sec enqueued
  dequeueRate: 9.8,              // Messages/sec dequeued
});

// Convenience methods
await queues.recordDepth("orders-queue", 150);
await queues.recordConsumerLag("orders-queue", 50);
await queues.recordDlq("orders-queue", 3);
await queues.recordProcessing("orders-queue", 1000, 5, 45);
```

### BullMQ Example

```typescript
import { Queue } from "bullmq";

const queue = new Queue("orders");

setInterval(async () => {
  const [waiting, active, failed, delayed] = await Promise.all([
    queue.getWaitingCount(),
    queue.getActiveCount(),
    queue.getFailedCount(),
    queue.getDelayedCount(),
  ]);

  await queues.recordMetrics("orders", {
    depth: waiting + delayed,
    processedCount: active,
    failedCount: failed,
  });
}, 60000);
```

---

## TypeScript Types

```typescript
import type {
  // Core client
  InfraSamuraiConfig,
  SendMetricOptions,
  SendMetricResponse,
  SendAlertOptions,
  SendAlertResponse,
  AlertPriority,

  // Queue metrics
  QueueConfig,
  QueueMetrics,
  QueueMetricsResponse,
} from "@infrasamurai/sdk-js";

// APM types (re-exported from OpenTelemetry)
import type { Span, Tracer, SpanContext } from "@infrasamurai/sdk-js/node";
```

---

## Runtime Compatibility

| Entry Point | Node.js | Browser | Deno | Bun |
|-------------|---------|---------|------|-----|
| `@infrasamurai/sdk-js` | ✅ | ✅ | ✅ | ✅ |
| `@infrasamurai/sdk-js/node` | ✅ | ❌ | ❌ | ⚠️ |
| `@infrasamurai/sdk-js/browser` | ❌ | ✅ | ❌ | ❌ |
| `@infrasamurai/sdk-js/queues` | ✅ | ❌ | ✅ | ✅ |

**Note:** Node.js APM auto-instrumentation uses monkey-patching via `require-in-the-middle`, which doesn't work in Deno and has limited support in Bun.

---

## Requirements

- Node.js 18+ (for native `fetch`) or browser environment
- TypeScript 5.0+ (for type definitions)

## Dependencies

The SDK uses OpenTelemetry for APM tracing:

**Required:**
- `@opentelemetry/api`
- `@opentelemetry/core`
- `@opentelemetry/resources`
- `@opentelemetry/sdk-trace-base`
- `@opentelemetry/exporter-trace-otlp-http`

**Optional (for auto-instrumentation):**
- `@opentelemetry/sdk-node`
- `@opentelemetry/auto-instrumentations-node`
- `@prisma/instrumentation`

---

## License

MIT