@march-ai/history-sdk
v0.1.1
Published
TypeScript SDK for March AI Conversation History API - Works in Node.js and browsers
Maintainers
Readme
March History SDK (TypeScript)
A universal TypeScript SDK for the March AI Conversation History API - Works in both Node.js and browsers
✨ 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
npm install @march/history-sdk
# or
yarn add @march/history-sdk
# or
pnpm add @march/history-sdk🚀 Quick Start
Node.js (Backend)
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)
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
// 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
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
// 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
// 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
// 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:
.list()- Returns a single page of results.listIter()- Returns an async generator that automatically fetches all pages
// 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:
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:
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
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
const client = new MarchHistoryClient({
baseUrl: 'http://localhost:8000',
customHeaders: {
'X-Custom-Header': 'value',
'X-Request-ID': crypto.randomUUID(),
},
});Manual Cleanup
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:
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:
// 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
fetchAPI (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
Built with ❤️ using TypeScript, Zod, and tsup.
