npm package: @lanternade/keelstat-sdk
Description: Lightweight observability SDK — traces, errors, events, metrics, and logs via OTLP to Axiom
Author: Lanternade
Latest version: 0.1.1

# @lanternade/keelstat-sdk

[![npm version](https://img.shields.io/npm/v/@lanternade/keelstat-sdk.svg)](https://www.npmjs.com/package/@lanternade/keelstat-sdk)
[![license](https://img.shields.io/npm/l/@lanternade/keelstat-sdk.svg)](https://github.com/lanternade/lanternade/blob/main/packages/keelstat-sdk/LICENSE)
[![bundle size](https://img.shields.io/bundlephobia/minzip/@lanternade/keelstat-sdk)](https://bundlephobia.com/package/@lanternade/keelstat-sdk)

Lightweight observability SDK — traces, errors, events, metrics, and logs via OTLP to [Axiom](https://axiom.co). Zero dependencies. Works in Node.js, Bun, Cloudflare Workers, Vercel Edge, and browsers.

## Install

```bash
npm install @lanternade/keelstat-sdk
```

## Quick Start

```ts
import { keelstat } from '@lanternade/keelstat-sdk';

// Set KEELSTAT_AXIOM_API_KEY in your environment — that's all you need.

const result = await keelstat.trace('checkout.process', async (span) => {
  span.setAttribute('cart.items', 3);
  return await processCheckout();
});

keelstat.error(new Error('Payment failed'), { userId: 'u123' });

await keelstat.flush(); // Call before serverless function exits
```

## API Reference

### `keelstat.trace(name, fn, options?)` — Wrapper Mode

Wraps an async function with a span. Automatically ends the span and captures errors.

```ts
const result = await keelstat.trace('db.query', async (span) => {
  span.setAttribute('db.statement', 'SELECT ...');
  return await db.query('SELECT ...');
});
```

Returns `Promise<T>` — the return value of `fn`.

### `keelstat.trace(name, options?)` — Manual Mode

Creates a span that you end yourself. Use when you can't wrap the work in a single function.

```ts
const span = keelstat.trace('background.job');
try {
  await doWork();
  span.setStatus({ code: 'ok' });
} catch (err) {
  span.setStatus({ code: 'error', message: err.message });
  throw err;
} finally {
  span.end();
}
```

Returns `KeelstatSpanHandle` with `setAttribute`, `setAttributes`, `addEvent`, `setStatus`, `end`, `traceId`, `spanId`.

#### Trace Options

| Option | Type | Description |
|--------|------|-------------|
| `kind` | `'internal' \| 'server' \| 'client' \| 'producer' \| 'consumer'` | Span kind (default: `'internal'`) |
| `attributes` | `Record<string, string \| number \| boolean>` | Initial span attributes |
| `parent` | `KeelstatSpanHandle` | Explicit parent span for manual nesting |

### `keelstat.error(error, context?)`

Records an error with automatic fingerprinting and deduplication.

```ts
keelstat.error(new Error('Connection timeout'), {
  userId: 'u123',
  level: 'fatal',
  attributes: { endpoint: '/api/checkout' },
});
```

Duplicate errors within a 60-second window are suppressed. Override with `{ force: true }`. Custom fingerprinting via `{ fingerprint: ['payment', 'stripe'] }`.

| Context Field | Type | Description |
|---------------|------|-------------|
| `userId` | `string` | User ID for attribution |
| `level` | `'debug' \| 'info' \| 'warn' \| 'error' \| 'fatal'` | Severity (default: `'error'`) |
| `attributes` | `Record<string, string \| number \| boolean>` | Additional attributes |
| `force` | `boolean` | Bypass deduplication |
| `fingerprint` | `string[]` | Custom fingerprint components |

### `keelstat.event(name, properties?)`

Records a structured event.

```ts
keelstat.event('button_clicked', {
  button: 'add_to_cart',
  product_id: 'sku_123',
});
```

### `keelstat.metric(name, value, options?)`

Records a metric data point.

```ts
keelstat.metric('queue.depth', 42, {
  type: 'gauge',
  unit: 'items',
  attributes: { queue: 'emails' },
});
```

| Option | Type | Description |
|--------|------|-------------|
| `type` | `'counter' \| 'gauge' \| 'histogram'` | Metric type (default: `'gauge'`) |
| `unit` | `string` | Unit of measurement |
| `attributes` | `Record<string, string \| number \| boolean>` | Dimensions |

### `keelstat.log(level, message, attributes?)`

Records a structured log with trace correlation.

```ts
keelstat.log('info', 'Order processed', { orderId: 'abc-123' });
```

Logs emitted inside a `trace()` block are automatically correlated via `traceId` and `spanId`.

### Lifecycle Methods

#### `keelstat.init(config?)`

Explicitly initialize with custom config. Optional — the SDK lazy-initializes from env vars on first method call.

```ts
import { keelstat, defineConfig } from '@lanternade/keelstat-sdk';

keelstat.init({
  apiKey: 'xaat-...',
  dataset: 'my-app',
  serviceName: 'checkout-service',
  debug: true,
});
```

#### `keelstat.flush()`

Flush all buffered signals. Call this before a serverless function exits.

```ts
await keelstat.flush();
```

#### `keelstat.shutdown()`

Graceful shutdown — flushes, stops timers, and resets state.

```ts
await keelstat.shutdown();
```

### Identity Methods

#### `keelstat.identify(userId, traits?)`

Associate a user with all subsequent signals.

```ts
keelstat.identify('user_123', { plan: 'pro', company: 'Acme' });
```

#### `keelstat.resetUser()`

Clear the current user identity.

```ts
keelstat.resetUser();
```

## Configuration

All options can be set via `defineConfig()`, `keelstat.init()`, or `KEELSTAT_*` environment variables. Resolution order: explicit config > env vars > auto-detect > defaults.

| Field | Type | Default | Env Var | Description |
|-------|------|---------|---------|-------------|
| `apiKey` | `string` | `''` | `KEELSTAT_AXIOM_API_KEY` | Axiom API key (required) |
| `endpoint` | `string` | `'https://api.axiom.co'` | `KEELSTAT_ENDPOINT` | OTLP endpoint |
| `dataset` | `string` | `'keelstat'` | `KEELSTAT_DATASET` | Axiom dataset name |
| `serviceName` | `string` | `'unknown-service'` | `KEELSTAT_SERVICE_NAME` | Service name |
| `serviceVersion` | `string` | `'0.0.0'` | `KEELSTAT_SERVICE_VERSION` | Service version (auto-detected from `VERCEL_GIT_COMMIT_SHA`, `GITHUB_SHA`) |
| `environment` | `string` | `'development'` | `KEELSTAT_ENVIRONMENT` | Deployment environment (auto-detected from `VERCEL_ENV`, `NODE_ENV`) |
| `sampleRate` | `number` | `1.0` | `KEELSTAT_SAMPLE_RATE` | Trace sample rate (0.0–1.0) |
| `enabled` | `boolean` | `true` | `KEELSTAT_ENABLED` | Enable/disable SDK |
| `debug` | `boolean` | `false` | `KEELSTAT_DEBUG` | Enable debug logging to console |
| `batchSize` | `number` | `100` | `KEELSTAT_BATCH_SIZE` | Batch size before auto-flush |
| `flushIntervalMs` | `number` | `5000` | `KEELSTAT_FLUSH_INTERVAL_MS` | Flush interval in ms |
| `maxRetries` | `number` | `3` | — | Max retry attempts for failed exports |
| `retryBaseDelayMs` | `number` | `1000` | — | Base retry delay in ms |
| `mode` | `'direct'` | `'direct'` | `KEELSTAT_MODE` | Export mode |
| `release` | `string` | `''` | `KEELSTAT_RELEASE` | Release identifier (auto-detected from git SHA) |
| `sessionCookieName` | `string` | `'keelstat_session'` | `KEELSTAT_SESSION_COOKIE_NAME` | Session cookie name |
| `sessionLocalStorageKey` | `string` | `'keelstat_device'` | `KEELSTAT_SESSION_LS_KEY` | Device ID localStorage key |
| `sessionTimeoutMs` | `number` | `1800000` | `KEELSTAT_SESSION_TIMEOUT_MS` | Session inactivity timeout (30 min) |
| `sessionSecureCookie` | `boolean` | auto | `KEELSTAT_SESSION_SECURE_COOKIE` | Secure flag on session cookie |

## Framework Guides

### Next.js

```ts
// lib/keelstat.ts
import { keelstat } from '@lanternade/keelstat-sdk';
export { keelstat };

// In a Server Action or Route Handler:
export async function POST(req: Request) {
  return keelstat.trace('api.checkout', async (span) => {
    const body = await req.json();
    span.setAttribute('cart.items', body.items.length);
    const result = await processCheckout(body);
    await keelstat.flush();
    return Response.json(result);
  });
}
```

### Express

```ts
import express from 'express';
import { keelstat } from '@lanternade/keelstat-sdk';

const app = express();

app.post('/checkout', async (req, res) => {
  await keelstat.trace('api.checkout', async (span) => {
    span.setAttribute('cart.items', req.body.items.length);
    const result = await processCheckout(req.body);
    res.json(result);
  });
});

process.on('SIGTERM', async () => {
  await keelstat.shutdown();
  process.exit(0);
});
```

### Cloudflare Workers

```ts
import { keelstat } from '@lanternade/keelstat-sdk';

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    keelstat.init({ apiKey: env.KEELSTAT_AXIOM_API_KEY });

    return keelstat.trace('worker.request', async (span) => {
      span.setAttribute('http.url', request.url);
      const response = await handleRequest(request);
      await keelstat.flush();
      return response;
    });
  },
};
```

### Browser

```ts
import { keelstat } from '@lanternade/keelstat-sdk';

keelstat.init({
  apiKey: 'xaat-...',  // Use a client-safe ingest token
  serviceName: 'my-spa',
});

document.querySelector('#checkout-btn')?.addEventListener('click', () => {
  keelstat.event('button_clicked', { button: 'checkout' });
});

// Flush on page unload
window.addEventListener('beforeunload', () => {
  keelstat.flush();
});
```

## TypeScript

The SDK is fully typed. All public types are exported:

```ts
import type {
  KeelstatSDK,
  KeelstatConfig,
  KeelstatConfigInput,
  KeelstatSpanHandle,
  TraceOptions,
  ErrorContext,
  MetricOptions,
  LogLevel,
  Attributes,
} from '@lanternade/keelstat-sdk';
```

## Safety Guarantees

- **Never throws** — all methods gracefully degrade to no-ops on failure
- **Silent when disabled** — `enabled: false` or missing API key produces zero network calls
- **No ambient pollution** — does not modify global types or objects
- **Zero dependencies** — the SDK is fully self-contained
- **Tree-shakeable** — marked `sideEffects: false`

## API Stability

Every export is annotated with a stability tier:

| Tier | Meaning |
|------|---------|
| `@public` | Stable. Breaking changes require a major version bump (post-1.0). |
| `@beta` | API shape may evolve in minor versions. Documented in changelog. |

The 5 core methods (`trace`, `error`, `event`, `metric`, `log`), lifecycle methods (`init`, `flush`, `shutdown`), `defineConfig`, and all associated types are **@public**.

Session management (`SessionManager`, `identify`, `resetUser`, `SessionConfig`, `SessionContext`) is **@beta** — the interface is functional but may change based on feedback.

Internal types (`BufferedSpan`, `Exporter`, `BatchProcessor`, etc.) are not exported and carry no stability guarantees.

See [API_REPORT.md](./API_REPORT.md) for the full export inventory.

## License

[MIT](./LICENSE)