npm package: @march-ai/history-sdk
Description: TypeScript SDK for March AI Conversation History API - Works in Node.js and browsers
Author: March Team
Latest version: 0.1.1

# March History SDK (TypeScript)

> A universal TypeScript SDK for the March AI Conversation History API - Works in both Node.js and browsers

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/)
[![Node.js >= 18](https://img.shields.io/badge/Node.js-%3E=18-green.svg)](https://nodejs.org/)

## ✨ Features

- 🌍 **Universal**: Works in Node.js (18+) and all modern browsers
- 🔐 **Dual Authentication**: Supports both API keys (backend) and JWT bearer tokens (frontend)
- 📘 **Full TypeScript Support**: Complete type safety with auto-generated types
- ✅ **Runtime Validation**: Uses Zod for request/response validation
- 🔄 **Auto-Retry**: Exponential backoff with jitter for transient failures
- 📄 **Auto-Pagination**: Async generators for seamless iteration
- 🎯 **Resource-Based API**: Clean, intuitive method organization
- 🪶 **Lightweight**: ~45KB total, ~12KB gzipped
- 🌲 **Tree-Shakeable**: Optimized for frontend bundles

## 📦 Installation

```bash
npm install @march/history-sdk
# or
yarn add @march/history-sdk
# or
pnpm add @march/history-sdk
```

## 🚀 Quick Start

### Node.js (Backend)

```typescript
import { MarchHistoryClient, MessageRole } from '@march/history-sdk';

// Initialize with API key
const client = new MarchHistoryClient({
  baseUrl: 'http://localhost:8000',
  apiKey: 'your-api-key', // Sent as X-API-Key header
});

// Create a conversation
const conversation = await client.conversations.create({
  tenant_name: 'acme-corp',
  user_id: 'user-123',
  title: 'Customer Support Session',
  metadata: { session_id: 'sess-456' },
});

// Add messages
await client.messages.create({
  conversation_id: conversation.id,
  role: MessageRole.USER,
  content: 'Hello, I need help!',
});

await client.messages.create({
  conversation_id: conversation.id,
  role: MessageRole.ASSISTANT,
  content: 'How can I help you today?',
});

// List messages
const messages = await client.messages.list({
  conversation_id: conversation.id,
});

console.log(`Found ${messages.length} messages`);
```

### Browser (Frontend)

```typescript
import { MarchHistoryClient, ConversationStatus } from '@march/history-sdk';

// Get JWT from your auth provider
const jwtToken = await getJWTToken();

// Initialize with bearer token
const client = new MarchHistoryClient({
  baseUrl: 'https://api.example.com',
  bearerToken: jwtToken, // Sent as Authorization: Bearer header
});

// List conversations
const conversations = await client.conversations.list({
  tenant_name: 'my-tenant',
  status: ConversationStatus.ACTIVE,
});

console.log(`Found ${conversations.length} conversations`);
```

### Dual Authentication

```typescript
// Some APIs require both API key and JWT
const client = new MarchHistoryClient({
  baseUrl: 'https://api.example.com',
  apiKey: 'project-api-key',     // X-API-Key header
  bearerToken: userJwtToken,      // Authorization: Bearer header
});

// Both headers will be sent with every request
```

## 📚 API Reference

### Client Initialization

```typescript
const client = new MarchHistoryClient({
  baseUrl?: string;           // Default: 'http://localhost:8000'
  timeout?: number;           // Default: 30000 (ms)
  maxRetries?: number;        // Default: 3
  apiKey?: string;            // Optional API key
  bearerToken?: string;       // Optional JWT token
  customHeaders?: Record<string, string>;
});
```

### Tenants

```typescript
// List tenants
const tenants = await client.tenants.list({ offset: 0, limit: 100 });

// Get tenant by ID
const tenant = await client.tenants.getById(1);

// Get tenant by name
const tenant = await client.tenants.getByName('acme-corp');

// Auto-pagination
for await (const tenant of client.tenants.listIter()) {
  console.log(tenant.name);
}
```

### Conversations

```typescript
// Create conversation
const conv = await client.conversations.create({
  tenant_name: 'acme-corp',
  user_id: 'user-123',
  title: 'Support Chat',
  agent_identifier: 'support-bot-v1',
  status: ConversationStatus.ACTIVE,
  metadata: { session_id: 'sess-456' },
});

// Get conversation
const conv = await client.conversations.getById(1);

// Get conversation with messages
const conv = await client.conversations.getById(1, true);

// List conversations
const convs = await client.conversations.list({
  tenant_name: 'acme-corp',
  status: ConversationStatus.ACTIVE,
  offset: 0,
  limit: 100,
});

// Update conversation
const updated = await client.conversations.update(1, {
  title: 'New Title',
  metadata: { updated: true },
});

// Archive/unarchive
await client.conversations.archive(1);
await client.conversations.unarchive(1);

// Delete conversation
await client.conversations.deleteById(1);

// Search conversations
const results = await client.conversations.search({
  q: 'support',
  status: ConversationStatus.ACTIVE,
});

// Auto-pagination
for await (const conv of client.conversations.listIter({ tenant_name: 'acme' })) {
  console.log(conv.title);
}
```

### Messages

```typescript
// Create single message
const msg = await client.messages.create({
  conversation_id: 1,
  role: MessageRole.USER,
  content: 'Hello!',
  metadata: { client_ip: '192.168.1.1' },
});

// Batch create messages
const messages = await client.messages.createBatch(1, [
  { role: MessageRole.USER, content: 'Hello!' },
  { role: MessageRole.ASSISTANT, content: 'Hi! How can I help?' },
]);

// List messages
const messages = await client.messages.list({
  conversation_id: 1,
  role: MessageRole.USER,
  offset: 0,
  limit: 100,
});

// Get message by ID
const msg = await client.messages.getById(1);

// Search messages in conversation
const results = await client.messages.search({
  conversation_id: 1,
  q: 'help',
  role: MessageRole.USER,
});

// Search messages globally
const results = await client.messages.searchGlobal({
  q: 'error',
  conversation_id: 1,
});

// Auto-pagination
for await (const msg of client.messages.listIter({ conversation_id: 1 })) {
  console.log(msg.content);
}
```

## 🔄 Auto-Pagination

The SDK provides two methods for listing resources:

1. **`.list()`** - Returns a single page of results
2. **`.listIter()`** - Returns an async generator that automatically fetches all pages

```typescript
// Manual pagination
const page1 = await client.conversations.list({ offset: 0, limit: 100 });
const page2 = await client.conversations.list({ offset: 100, limit: 100 });

// Auto-pagination (recommended)
for await (const conversation of client.conversations.listIter()) {
  console.log(conversation.title);
  // Automatically fetches next page when needed
}

// Iterate by pages
for await (const page of client.conversations.listIter().pages()) {
  console.log(`Page has ${page.length} items`);
}

// Limit total items fetched
const paginator = client.conversations.listIter({}, 100, 500); // pageSize=100, maxItems=500
for await (const conv of paginator) {
  // Will stop after 500 items
}
```

## 🚨 Error Handling

The SDK provides specific error classes for different HTTP status codes:

```typescript
import {
  NotFoundError,
  ValidationError,
  ConflictError,
  ServerError,
  NetworkError,
  RetryError,
} from '@march/history-sdk';

try {
  const conv = await client.conversations.getById(999);
} catch (error) {
  if (error instanceof NotFoundError) {
    console.error('Conversation not found:', error.statusCode);
  } else if (error instanceof ValidationError) {
    console.error('Invalid request:', error.details);
  } else if (error instanceof ConflictError) {
    console.error('Duplicate sequence number:', error.message);
  } else if (error instanceof ServerError) {
    console.error('Server error:', error.statusCode);
  } else if (error instanceof NetworkError) {
    console.error('Network error:', error.message);
  } else if (error instanceof RetryError) {
    console.error('Max retries exceeded:', error.lastException);
  } else {
    console.error('Unexpected error:', error);
  }
}
```

### Client-Side Validation

The SDK validates request data using Zod before sending to the API:

```typescript
try {
  await client.messages.create({
    conversation_id: 1,
    role: 'invalid_role', // Invalid role
    content: 'test',
  });
} catch (error) {
  // ZodError: Validation failed
  console.error('Client-side validation failed:', error);
}
```

## 🔧 Advanced Configuration

### Custom Retry Configuration

```typescript
const client = new MarchHistoryClient({
  baseUrl: 'http://localhost:8000',
  retryConfig: {
    maxRetries: 5,
    backoffFactor: 2.0,
    retryStatusCodes: [408, 429, 500, 502, 503, 504],
    maxBackoffSeconds: 120,
  },
});
```

### Custom Headers

```typescript
const client = new MarchHistoryClient({
  baseUrl: 'http://localhost:8000',
  customHeaders: {
    'X-Custom-Header': 'value',
    'X-Request-ID': crypto.randomUUID(),
  },
});
```

### Manual Cleanup

```typescript
const client = new MarchHistoryClient({ baseUrl: 'http://localhost:8000' });

// Use the client...
await client.conversations.list();

// Optional: Explicitly close connections
client.close();
```

## 📝 TypeScript Types

The SDK exports all TypeScript types for use in your code:

```typescript
import type {
  Tenant,
  Conversation,
  Message,
  ConversationStatus,
  MessageRole,
  CreateConversationParams,
  ListConversationsParams,
  SearchConversationsParams,
  CreateMessageParams,
  ListMessagesParams,
} from '@march/history-sdk';

const params: CreateConversationParams = {
  tenant_name: 'acme-corp',
  user_id: 'user-123',
  title: 'Support Chat',
  metadata: { session_id: 'sess-456' },
};

const conversation: Conversation = await client.conversations.create(params);
```

## 🌐 Browser Usage

The SDK works seamlessly in browsers. Here's an example with Vite:

```typescript
// src/main.ts
import { MarchHistoryClient, MessageRole } from '@march/history-sdk';

async function main() {
  const client = new MarchHistoryClient({
    baseUrl: 'https://api.example.com',
    bearerToken: localStorage.getItem('jwt_token'),
  });

  const conversations = await client.conversations.list({
    tenant_name: 'my-tenant',
  });

  console.log('Conversations:', conversations);
}

main().catch(console.error);
```

## 📊 Bundle Size

- **Unminified**: ~138 KB (ESM), ~139 KB (CJS)
- **With Zod**: ~45-50 KB total
- **Gzipped**: ~12-15 KB
- **Tree-shakeable**: Only import what you use

## 🏗️ Architecture

The SDK follows these design patterns:

- **Resource-based organization**: Each API domain (tenants, conversations, messages) is a separate resource class
- **Universal compatibility**: Uses native `fetch` API (Node.js 18+ and all browsers)
- **Type safety**: Zod schemas provide runtime validation and auto-generate TypeScript types
- **Async-first design**: All operations use `async/await`
- **Retry logic**: Exponential backoff with jitter for transient failures
- **Error handling**: Status code → specific error class mapping

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## 📄 License

MIT © March Team

## 🔗 Links

- [API Documentation](API_DOCUMENTATION.md)
- [GitHub Repository](#)
- [NPM Package](#)

---

Built with ❤️ using TypeScript, Zod, and tsup.