npm package: @myo-devs/node
Description: Official Node.js SDK for the Myo Platform
Author: Myo
Latest version: 0.1.0

# Myo Node.js SDK

Official Node.js SDK for the [Myo Platform](https://myo-app.co) - Connect your users' personal data and retrieve structured insights via a simple API.

## Installation

```bash
npm install @myo/node
```

## Getting Started

### 1. Get your API key

Sign up at [myo-app.co](https://myo-app.co) to get your developer API key (`dev_xxx`).

### 2. Set up environment variables

```bash
MYO_API_KEY=dev_xxx
MYO_API_BASE_URL=https://api.myo-app.co  # Optional, defaults to production
```

### 3. Initialize the client

```typescript
import { MyoClient } from '@myo/node';

const client = new MyoClient({
  apiKey: process.env.MYO_API_KEY,
});
```

### 4. Create a link token (for user consent)

Before querying a user's data, they must connect via the Myo widget. Create a link token to initiate this flow:

```typescript
const linkToken = await client.link.tokens.create({
  redirectUri: 'https://yourapp.com/callback',
});

// Return to your frontend:
// - linkToken.linkToken: Pass to @myo/react widget
// - linkToken.widgetUrl: Or redirect user directly
// - linkToken.expiresAt: Token expiration time
```

### 5. Query user context

After a user has connected, query their data with natural language:

```typescript
const response = await client.context.query({
  userId: 'user-uuid-from-widget-callback',
  query: 'What are this user\'s main interests?',
});

console.log(response.response);  // AI-generated answer
console.log(response.model);     // Model used
```

## Real-World Example (Next.js)

Here's how to integrate Myo in a Next.js application with API routes:

### API Route: Create Link Token

```typescript
// app/api/link-token/route.ts
import { NextResponse } from 'next/server';
import { MyoClient } from '@myo/node';

export async function POST() {
  const client = new MyoClient({
    apiKey: process.env.MYO_API_KEY,
  });

  const linkToken = await client.link.tokens.create({
    redirectUri: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
  });

  return NextResponse.json({
    linkToken: linkToken.linkToken,
    widgetUrl: linkToken.widgetUrl,
    expiresAt: linkToken.expiresAt,
  });
}
```

### API Route: Query User Context

```typescript
// app/api/query/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { MyoClient } from '@myo/node';

export async function POST(request: NextRequest) {
  const { userId, query } = await request.json();

  if (!userId || !query) {
    return NextResponse.json(
      { error: 'userId and query are required' },
      { status: 400 }
    );
  }

  const client = new MyoClient({
    apiKey: process.env.MYO_API_KEY,
  });

  const response = await client.context.query({
    userId,
    query,
  });

  return NextResponse.json({
    response: response.response,
    model: response.model,
  });
}
```

## Features

- **Intuitive API** - `client.link.tokens.create()`, not `client.createLinkToken()`
- **Full TypeScript support** - Excellent IDE autocompletion
- **Dual ESM/CJS** - Works with both module systems
- **Automatic retries** - Built-in retry logic with exponential backoff
- **Webhook utilities** - `verifyWebhookSignature()` for secure webhook handling
- **Actionable errors** - Clear error messages that tell you how to fix issues

## API Reference

### Creating Link Tokens

Link tokens are used to initialize the frontend widget for user consent:

```typescript
const token = await client.link.tokens.create({
  redirectUri: 'https://myapp.com/callback',
  userEmail: 'user@example.com',  // Optional: pre-fill email
  webhookUrl: 'https://myapp.com/webhooks/myo',  // Optional: override default
  state: 'user_123',  // Optional: your internal reference
});

// Pass token.linkToken to your frontend
// Or redirect user to token.widgetUrl
```

### Querying User Context

After a user has connected, query their data with natural language:

```typescript
const response = await client.context.query({
  userId: '550e8400-e29b-41d4-a716-446655440000',
  query: 'What are this user\'s main interests?',
});
console.log(response.response);  // AI-generated answer
console.log(response.model);     // Model used
```

### Multi-turn Conversations

```typescript
// First query
const response1 = await client.context.query({
  userId,
  query: 'What does this user like to do?',
});

// Follow-up query with context
const response2 = await client.context.query({
  userId,
  query: 'Can you elaborate on the first hobby?',
  conversationHistory: [
    { role: 'user', content: 'What does this user like to do?' },
    { role: 'assistant', content: response1.response },
  ],
});
```

### Configuring Webhooks

```typescript
const config = await client.webhooks.configure(
  'https://myapp.com/webhooks/myo'
);
console.log(`Secret: ${config.webhookSecret}`);
// Store this secret securely for signature verification
```

### Verifying Webhook Signatures

```typescript
import { constructWebhookEvent, WebhookVerificationError } from '@myo/node';
import express from 'express';

const app = express();

app.post('/webhooks/myo', express.raw({ type: 'application/json' }), (req, res) => {
  try {
    const event = constructWebhookEvent(
      req.body,
      req.headers['x-myo-webhook-signature'] as string,
      process.env.WEBHOOK_SECRET!,
      { timestampHeader: req.headers['x-myo-webhook-timestamp'] as string }
    );

    if (event.type === 'link.completed') {
      const userId = event.data.user_id;
      // Handle the link completion
    }
  } catch (err) {
    if (err instanceof WebhookVerificationError) {
      return res.status(400).send(err.message);
    }
    throw err;
  }

  res.sendStatus(200);
});
```

## Error Handling

The SDK raises specific exceptions for different error types:

```typescript
import {
  MyoError,            // Base error
  AuthenticationError, // Invalid API key (401)
  PermissionError,     // No access to resource (403)
  NotFoundError,       // Resource not found (404)
  RateLimitError,      // Rate limit exceeded (429)
  ValidationError,     // Invalid parameters (422)
  APIError,            // Server errors (5xx)
} from '@myo/node';

try {
  const response = await client.context.query({ userId, query });
} catch (err) {
  if (err instanceof AuthenticationError) {
    console.log(`Check your API key: ${err.message}`);
  } else if (err instanceof PermissionError) {
    console.log(`User hasn't linked yet: ${err.message}`);
  } else if (err instanceof RateLimitError) {
    console.log(`Slow down! Retry after ${err.retryAfter} seconds`);
  } else if (err instanceof MyoError) {
    console.log(`API error: ${err.message}`);
  }
}
```

## Configuration

```typescript
const client = new MyoClient({
  apiKey: 'dev_xxx',                    // Or set MYO_API_KEY env var
  baseUrl: 'https://api.myo.dev',       // Or set MYO_BASE_URL env var
  timeout: 30000,                       // Request timeout in ms
  maxRetries: 3,                        // Retry attempts for failed requests
});
```

## Requirements

- Node.js 18+

## License

MIT